1 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
  2 (function(win) {
  3 	var whiteSpaceRe = /^\s*|\s*$/g,
  4 		undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
  5 
  6 	var tinymce = {
  7 		majorVersion : '3',
  8 
  9 		minorVersion : '5.4.1',
 10 
 11 		releaseDate : '2012-06-24',
 12 
 13 		_init : function() {
 14 			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
 15 
 16 			t.isOpera = win.opera && opera.buildNumber;
 17 
 18 			t.isWebKit = /WebKit/.test(ua);
 19 
 20 			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
 21 
 22 			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
 23 
 24 			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
 25 
 26 			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
 27 
 28 			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
 29 
 30 			t.isGecko = !t.isWebKit && /Gecko/.test(ua);
 31 
 32 			t.isMac = ua.indexOf('Mac') != -1;
 33 
 34 			t.isAir = /adobeair/i.test(ua);
 35 
 36 			t.isIDevice = /(iPad|iPhone)/.test(ua);
 37 			
 38 			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
 39 
 40 			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
 41 			if (win.tinyMCEPreInit) {
 42 				t.suffix = tinyMCEPreInit.suffix;
 43 				t.baseURL = tinyMCEPreInit.base;
 44 				t.query = tinyMCEPreInit.query;
 45 				return;
 46 			}
 47 
 48 			// Get suffix and base
 49 			t.suffix = '';
 50 
 51 			// If base element found, add that infront of baseURL
 52 			nl = d.getElementsByTagName('base');
 53 			for (i=0; i<nl.length; i++) {
 54 				v = nl[i].href;
 55 				if (v) {
 56 					// Host only value like http://site.com or http://site.com:8008
 57 					if (/^https?:\/\/[^\/]+$/.test(v))
 58 						v += '/';
 59 
 60 					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
 61 				}
 62 			}
 63 
 64 			function getBase(n) {
 65 				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
 66 					if (/_(src|dev)\.js/g.test(n.src))
 67 						t.suffix = '_src';
 68 
 69 					if ((p = n.src.indexOf('?')) != -1)
 70 						t.query = n.src.substring(p + 1);
 71 
 72 					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
 73 
 74 					// If path to script is relative and a base href was found add that one infront
 75 					// the src property will always be an absolute one on non IE browsers and IE 8
 76 					// so this logic will basically only be executed on older IE versions
 77 					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
 78 						t.baseURL = base + t.baseURL;
 79 
 80 					return t.baseURL;
 81 				}
 82 
 83 				return null;
 84 			};
 85 
 86 			// Check document
 87 			nl = d.getElementsByTagName('script');
 88 			for (i=0; i<nl.length; i++) {
 89 				if (getBase(nl[i]))
 90 					return;
 91 			}
 92 
 93 			// Check head
 94 			n = d.getElementsByTagName('head')[0];
 95 			if (n) {
 96 				nl = n.getElementsByTagName('script');
 97 				for (i=0; i<nl.length; i++) {
 98 					if (getBase(nl[i]))
 99 						return;
100 				}
101 			}
102 
103 			return;
104 		},
105 
106 		is : function(o, t) {
107 			if (!t)
108 				return o !== undef;
109 
110 			if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
111 				return true;
112 
113 			return typeof(o) == t;
114 		},
115 
116 		makeMap : function(items, delim, map) {
117 			var i;
118 
119 			items = items || [];
120 			delim = delim || ',';
121 
122 			if (typeof(items) == "string")
123 				items = items.split(delim);
124 
125 			map = map || {};
126 
127 			i = items.length;
128 			while (i--)
129 				map[items[i]] = {};
130 
131 			return map;
132 		},
133 
134 		each : function(o, cb, s) {
135 			var n, l;
136 
137 			if (!o)
138 				return 0;
139 
140 			s = s || o;
141 
142 			if (o.length !== undef) {
143 				// Indexed arrays, needed for Safari
144 				for (n=0, l = o.length; n < l; n++) {
145 					if (cb.call(s, o[n], n, o) === false)
146 						return 0;
147 				}
148 			} else {
149 				// Hashtables
150 				for (n in o) {
151 					if (o.hasOwnProperty(n)) {
152 						if (cb.call(s, o[n], n, o) === false)
153 							return 0;
154 					}
155 				}
156 			}
157 
158 			return 1;
159 		},
160 
161 
162 		map : function(a, f) {
163 			var o = [];
164 
165 			tinymce.each(a, function(v) {
166 				o.push(f(v));
167 			});
168 
169 			return o;
170 		},
171 
172 		grep : function(a, f) {
173 			var o = [];
174 
175 			tinymce.each(a, function(v) {
176 				if (!f || f(v))
177 					o.push(v);
178 			});
179 
180 			return o;
181 		},
182 
183 		inArray : function(a, v) {
184 			var i, l;
185 
186 			if (a) {
187 				for (i = 0, l = a.length; i < l; i++) {
188 					if (a[i] === v)
189 						return i;
190 				}
191 			}
192 
193 			return -1;
194 		},
195 
196 		extend : function(obj, ext) {
197 			var i, l, name, args = arguments, value;
198 
199 			for (i = 1, l = args.length; i < l; i++) {
200 				ext = args[i];
201 				for (name in ext) {
202 					if (ext.hasOwnProperty(name)) {
203 						value = ext[name];
204 
205 						if (value !== undef) {
206 							obj[name] = value;
207 						}
208 					}
209 				}
210 			}
211 
212 			return obj;
213 		},
214 
215 
216 		trim : function(s) {
217 			return (s ? '' + s : '').replace(whiteSpaceRe, '');
218 		},
219 
220 		create : function(s, p, root) {
221 			var t = this, sp, ns, cn, scn, c, de = 0;
222 
223 			// Parse : <prefix> <class>:<super class>
224 			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
225 			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
226 
227 			// Create namespace for new class
228 			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
229 
230 			// Class already exists
231 			if (ns[cn])
232 				return;
233 
234 			// Make pure static class
235 			if (s[2] == 'static') {
236 				ns[cn] = p;
237 
238 				if (this.onCreate)
239 					this.onCreate(s[2], s[3], ns[cn]);
240 
241 				return;
242 			}
243 
244 			// Create default constructor
245 			if (!p[cn]) {
246 				p[cn] = function() {};
247 				de = 1;
248 			}
249 
250 			// Add constructor and methods
251 			ns[cn] = p[cn];
252 			t.extend(ns[cn].prototype, p);
253 
254 			// Extend
255 			if (s[5]) {
256 				sp = t.resolve(s[5]).prototype;
257 				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
258 
259 				// Extend constructor
260 				c = ns[cn];
261 				if (de) {
262 					// Add passthrough constructor
263 					ns[cn] = function() {
264 						return sp[scn].apply(this, arguments);
265 					};
266 				} else {
267 					// Add inherit constructor
268 					ns[cn] = function() {
269 						this.parent = sp[scn];
270 						return c.apply(this, arguments);
271 					};
272 				}
273 				ns[cn].prototype[cn] = ns[cn];
274 
275 				// Add super methods
276 				t.each(sp, function(f, n) {
277 					ns[cn].prototype[n] = sp[n];
278 				});
279 
280 				// Add overridden methods
281 				t.each(p, function(f, n) {
282 					// Extend methods if needed
283 					if (sp[n]) {
284 						ns[cn].prototype[n] = function() {
285 							this.parent = sp[n];
286 							return f.apply(this, arguments);
287 						};
288 					} else {
289 						if (n != cn)
290 							ns[cn].prototype[n] = f;
291 					}
292 				});
293 			}
294 
295 			// Add static methods
296 			t.each(p['static'], function(f, n) {
297 				ns[cn][n] = f;
298 			});
299 
300 			if (this.onCreate)
301 				this.onCreate(s[2], s[3], ns[cn].prototype);
302 		},
303 
304 		walk : function(o, f, n, s) {
305 			s = s || this;
306 
307 			if (o) {
308 				if (n)
309 					o = o[n];
310 
311 				tinymce.each(o, function(o, i) {
312 					if (f.call(s, o, i, n) === false)
313 						return false;
314 
315 					tinymce.walk(o, f, n, s);
316 				});
317 			}
318 		},
319 
320 		createNS : function(n, o) {
321 			var i, v;
322 
323 			o = o || win;
324 
325 			n = n.split('.');
326 			for (i=0; i<n.length; i++) {
327 				v = n[i];
328 
329 				if (!o[v])
330 					o[v] = {};
331 
332 				o = o[v];
333 			}
334 
335 			return o;
336 		},
337 
338 		resolve : function(n, o) {
339 			var i, l;
340 
341 			o = o || win;
342 
343 			n = n.split('.');
344 			for (i = 0, l = n.length; i < l; i++) {
345 				o = o[n[i]];
346 
347 				if (!o)
348 					break;
349 			}
350 
351 			return o;
352 		},
353 
354 		addUnload : function(f, s) {
355 			var t = this, unload;
356 
357 			unload = function() {
358 				var li = t.unloads, o, n;
359 
360 				if (li) {
361 					// Call unload handlers
362 					for (n in li) {
363 						o = li[n];
364 
365 						if (o && o.func)
366 							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
367 					}
368 
369 					// Detach unload function
370 					if (win.detachEvent) {
371 						win.detachEvent('onbeforeunload', fakeUnload);
372 						win.detachEvent('onunload', unload);
373 					} else if (win.removeEventListener)
374 						win.removeEventListener('unload', unload, false);
375 
376 					// Destroy references
377 					t.unloads = o = li = w = unload = 0;
378 
379 					// Run garbarge collector on IE
380 					if (win.CollectGarbage)
381 						CollectGarbage();
382 				}
383 			};
384 
385 			function fakeUnload() {
386 				var d = document;
387 
388 				function stop() {
389 					// Prevent memory leak
390 					d.detachEvent('onstop', stop);
391 
392 					// Call unload handler
393 					if (unload)
394 						unload();
395 
396 					d = 0;
397 				};
398 
399 				// Is there things still loading, then do some magic
400 				if (d.readyState == 'interactive') {
401 					// Fire unload when the currently loading page is stopped
402 					if (d)
403 						d.attachEvent('onstop', stop);
404 
405 					// Remove onstop listener after a while to prevent the unload function
406 					// to execute if the user presses cancel in an onbeforeunload
407 					// confirm dialog and then presses the browser stop button
408 					win.setTimeout(function() {
409 						if (d)
410 							d.detachEvent('onstop', stop);
411 					}, 0);
412 				}
413 			};
414 
415 			f = {func : f, scope : s || this};
416 
417 			if (!t.unloads) {
418 				// Attach unload handler
419 				if (win.attachEvent) {
420 					win.attachEvent('onunload', unload);
421 					win.attachEvent('onbeforeunload', fakeUnload);
422 				} else if (win.addEventListener)
423 					win.addEventListener('unload', unload, false);
424 
425 				// Setup initial unload handler array
426 				t.unloads = [f];
427 			} else
428 				t.unloads.push(f);
429 
430 			return f;
431 		},
432 
433 		removeUnload : function(f) {
434 			var u = this.unloads, r = null;
435 
436 			tinymce.each(u, function(o, i) {
437 				if (o && o.func == f) {
438 					u.splice(i, 1);
439 					r = f;
440 					return false;
441 				}
442 			});
443 
444 			return r;
445 		},
446 
447 		explode : function(s, d) {
448 			if (!s || tinymce.is(s, 'array')) {
449 				return s;
450 			}
451 
452 			return tinymce.map(s.split(d || ','), tinymce.trim);
453 		},
454 
455 		_addVer : function(u) {
456 			var v;
457 
458 			if (!this.query)
459 				return u;
460 
461 			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
462 
463 			if (u.indexOf('#') == -1)
464 				return u + v;
465 
466 			return u.replace('#', v + '#');
467 		},
468 
469 		// Fix function for IE 9 where regexps isn't working correctly
470 		// Todo: remove me once MS fixes the bug
471 		_replace : function(find, replace, str) {
472 			// On IE9 we have to fake $x replacement
473 			if (isRegExpBroken) {
474 				return str.replace(find, function() {
475 					var val = replace, args = arguments, i;
476 
477 					for (i = 0; i < args.length - 2; i++) {
478 						if (args[i] === undef) {
479 							val = val.replace(new RegExp('\\$' + i, 'g'), '');
480 						} else {
481 							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
482 						}
483 					}
484 
485 					return val;
486 				});
487 			}
488 
489 			return str.replace(find, replace);
490 		}
491 
492 		};
493 
494 	// Initialize the API
495 	tinymce._init();
496 
497 	// Expose tinymce namespace to the global namespace (window)
498 	win.tinymce = win.tinyMCE = tinymce;
499 
500 	// Describe the different namespaces
501 
502 	})(window);
503 
504 
505 
506 (function() {
507 	if (!window.Prototype)
508 		return alert("Load prototype first!");
509 
510 	// Patch in core NS functions
511 	tinymce.extend(tinymce, {
512 		trim : function(s) {return s ? s.strip() : '';},
513 		inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;}
514 	});
515 
516 	// Patch in functions in various clases
517 	// Add a "#ifndefjquery" statement around each core API function you add below
518 	var patches = {
519 		'tinymce.util.JSON' : {
520 			/*serialize : function(o) {
521 				return o.toJSON();
522 			}*/
523 		}
524 	};
525 
526 	// Patch functions after a class is created
527 	tinymce.onCreate = function(ty, c, p) {
528 		tinymce.extend(p, patches[c]);
529 	};
530 })();
531 
532 
533 tinymce.create('tinymce.util.Dispatcher', {
534 	scope : null,
535 	listeners : null,
536 	inDispatch: false,
537 
538 	Dispatcher : function(scope) {
539 		this.scope = scope || this;
540 		this.listeners = [];
541 	},
542 
543 	add : function(callback, scope) {
544 		this.listeners.push({cb : callback, scope : scope || this.scope});
545 
546 		return callback;
547 	},
548 
549 	addToTop : function(callback, scope) {
550 		var self = this, listener = {cb : callback, scope : scope || self.scope};
551 
552 		// Create new listeners if addToTop is executed in a dispatch loop
553 		if (self.inDispatch) {
554 			self.listeners = [listener].concat(self.listeners);
555 		} else {
556 			self.listeners.unshift(listener);
557 		}
558 
559 		return callback;
560 	},
561 
562 	remove : function(callback) {
563 		var listeners = this.listeners, output = null;
564 
565 		tinymce.each(listeners, function(listener, i) {
566 			if (callback == listener.cb) {
567 				output = listener;
568 				listeners.splice(i, 1);
569 				return false;
570 			}
571 		});
572 
573 		return output;
574 	},
575 
576 	dispatch : function() {
577 		var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
578 
579 		self.inDispatch = true;
580 		
581 		// Needs to be a real loop since the listener count might change while looping
582 		// And this is also more efficient
583 		for (i = 0; i < listeners.length; i++) {
584 			listener = listeners[i];
585 			returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
586 
587 			if (returnValue === false)
588 				break;
589 		}
590 
591 		self.inDispatch = false;
592 
593 		return returnValue;
594 	}
595 
596 	});
597 
598 (function() {
599 	var each = tinymce.each;
600 
601 	tinymce.create('tinymce.util.URI', {
602 		URI : function(u, s) {
603 			var t = this, o, a, b, base_url;
604 
605 			// Trim whitespace
606 			u = tinymce.trim(u);
607 
608 			// Default settings
609 			s = t.settings = s || {};
610 
611 			// Strange app protocol that isn't http/https or local anchor
612 			// For example: mailto,skype,tel etc.
613 			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
614 				t.source = u;
615 				return;
616 			}
617 
618 			// Absolute path with no host, fake host and protocol
619 			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
620 				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
621 
622 			// Relative path http:// or protocol relative //path
623 			if (!/^[\w\-]*:?\/\//.test(u)) {
624 				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
625 				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
626 			}
627 
628 			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
629 			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
630 			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
631 			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
632 				var s = u[i];
633 
634 				// Zope 3 workaround, they use @@something
635 				if (s)
636 					s = s.replace(/\(mce_at\)/g, '@@');
637 
638 				t[v] = s;
639 			});
640 
641 			b = s.base_uri;
642 			if (b) {
643 				if (!t.protocol)
644 					t.protocol = b.protocol;
645 
646 				if (!t.userInfo)
647 					t.userInfo = b.userInfo;
648 
649 				if (!t.port && t.host === 'mce_host')
650 					t.port = b.port;
651 
652 				if (!t.host || t.host === 'mce_host')
653 					t.host = b.host;
654 
655 				t.source = '';
656 			}
657 
658 			//t.path = t.path || '/';
659 		},
660 
661 		setPath : function(p) {
662 			var t = this;
663 
664 			p = /^(.*?)\/?(\w+)?$/.exec(p);
665 
666 			// Update path parts
667 			t.path = p[0];
668 			t.directory = p[1];
669 			t.file = p[2];
670 
671 			// Rebuild source
672 			t.source = '';
673 			t.getURI();
674 		},
675 
676 		toRelative : function(u) {
677 			var t = this, o;
678 
679 			if (u === "./")
680 				return u;
681 
682 			u = new tinymce.util.URI(u, {base_uri : t});
683 
684 			// Not on same domain/port or protocol
685 			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
686 				return u.getURI();
687 
688 			var tu = t.getURI(), uu = u.getURI();
689 			
690 			// Allow usage of the base_uri when relative_urls = true
691 			if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
692 				return tu;
693 
694 			o = t.toRelPath(t.path, u.path);
695 
696 			// Add query
697 			if (u.query)
698 				o += '?' + u.query;
699 
700 			// Add anchor
701 			if (u.anchor)
702 				o += '#' + u.anchor;
703 
704 			return o;
705 		},
706 	
707 		toAbsolute : function(u, nh) {
708 			u = new tinymce.util.URI(u, {base_uri : this});
709 
710 			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
711 		},
712 
713 		toRelPath : function(base, path) {
714 			var items, bp = 0, out = '', i, l;
715 
716 			// Split the paths
717 			base = base.substring(0, base.lastIndexOf('/'));
718 			base = base.split('/');
719 			items = path.split('/');
720 
721 			if (base.length >= items.length) {
722 				for (i = 0, l = base.length; i < l; i++) {
723 					if (i >= items.length || base[i] != items[i]) {
724 						bp = i + 1;
725 						break;
726 					}
727 				}
728 			}
729 
730 			if (base.length < items.length) {
731 				for (i = 0, l = items.length; i < l; i++) {
732 					if (i >= base.length || base[i] != items[i]) {
733 						bp = i + 1;
734 						break;
735 					}
736 				}
737 			}
738 
739 			if (bp === 1)
740 				return path;
741 
742 			for (i = 0, l = base.length - (bp - 1); i < l; i++)
743 				out += "../";
744 
745 			for (i = bp - 1, l = items.length; i < l; i++) {
746 				if (i != bp - 1)
747 					out += "/" + items[i];
748 				else
749 					out += items[i];
750 			}
751 
752 			return out;
753 		},
754 
755 		toAbsPath : function(base, path) {
756 			var i, nb = 0, o = [], tr, outPath;
757 
758 			// Split paths
759 			tr = /\/$/.test(path) ? '/' : '';
760 			base = base.split('/');
761 			path = path.split('/');
762 
763 			// Remove empty chunks
764 			each(base, function(k) {
765 				if (k)
766 					o.push(k);
767 			});
768 
769 			base = o;
770 
771 			// Merge relURLParts chunks
772 			for (i = path.length - 1, o = []; i >= 0; i--) {
773 				// Ignore empty or .
774 				if (path[i].length === 0 || path[i] === ".")
775 					continue;
776 
777 				// Is parent
778 				if (path[i] === '..') {
779 					nb++;
780 					continue;
781 				}
782 
783 				// Move up
784 				if (nb > 0) {
785 					nb--;
786 					continue;
787 				}
788 
789 				o.push(path[i]);
790 			}
791 
792 			i = base.length - nb;
793 
794 			// If /a/b/c or /
795 			if (i <= 0)
796 				outPath = o.reverse().join('/');
797 			else
798 				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
799 
800 			// Add front / if it's needed
801 			if (outPath.indexOf('/') !== 0)
802 				outPath = '/' + outPath;
803 
804 			// Add traling / if it's needed
805 			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
806 				outPath += tr;
807 
808 			return outPath;
809 		},
810 
811 		getURI : function(nh) {
812 			var s, t = this;
813 
814 			// Rebuild source
815 			if (!t.source || nh) {
816 				s = '';
817 
818 				if (!nh) {
819 					if (t.protocol)
820 						s += t.protocol + '://';
821 
822 					if (t.userInfo)
823 						s += t.userInfo + '@';
824 
825 					if (t.host)
826 						s += t.host;
827 
828 					if (t.port)
829 						s += ':' + t.port;
830 				}
831 
832 				if (t.path)
833 					s += t.path;
834 
835 				if (t.query)
836 					s += '?' + t.query;
837 
838 				if (t.anchor)
839 					s += '#' + t.anchor;
840 
841 				t.source = s;
842 			}
843 
844 			return t.source;
845 		}
846 	});
847 })();
848 
849 (function() {
850 	var each = tinymce.each;
851 
852 	tinymce.create('static tinymce.util.Cookie', {
853 		getHash : function(n) {
854 			var v = this.get(n), h;
855 
856 			if (v) {
857 				each(v.split('&'), function(v) {
858 					v = v.split('=');
859 					h = h || {};
860 					h[unescape(v[0])] = unescape(v[1]);
861 				});
862 			}
863 
864 			return h;
865 		},
866 
867 		setHash : function(n, v, e, p, d, s) {
868 			var o = '';
869 
870 			each(v, function(v, k) {
871 				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
872 			});
873 
874 			this.set(n, o, e, p, d, s);
875 		},
876 
877 		get : function(n) {
878 			var c = document.cookie, e, p = n + "=", b;
879 
880 			// Strict mode
881 			if (!c)
882 				return;
883 
884 			b = c.indexOf("; " + p);
885 
886 			if (b == -1) {
887 				b = c.indexOf(p);
888 
889 				if (b !== 0)
890 					return null;
891 			} else
892 				b += 2;
893 
894 			e = c.indexOf(";", b);
895 
896 			if (e == -1)
897 				e = c.length;
898 
899 			return unescape(c.substring(b + p.length, e));
900 		},
901 
902 		set : function(n, v, e, p, d, s) {
903 			document.cookie = n + "=" + escape(v) +
904 				((e) ? "; expires=" + e.toGMTString() : "") +
905 				((p) ? "; path=" + escape(p) : "") +
906 				((d) ? "; domain=" + d : "") +
907 				((s) ? "; secure" : "");
908 		},
909 
910 		remove : function(name, path, domain) {
911 			var date = new Date();
912 
913 			date.setTime(date.getTime() - 1000);
914 
915 			this.set(name, '', date, path, domain);
916 		}
917 	});
918 })();
919 
920 (function() {
921 	function serialize(o, quote) {
922 		var i, v, t, name;
923 
924 		quote = quote || '"';
925 
926 		if (o == null)
927 			return 'null';
928 
929 		t = typeof o;
930 
931 		if (t == 'string') {
932 			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
933 
934 			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
935 				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
936 				if (quote === '"' && a === "'")
937 					return a;
938 
939 				i = v.indexOf(b);
940 
941 				if (i + 1)
942 					return '\\' + v.charAt(i + 1);
943 
944 				a = b.charCodeAt().toString(16);
945 
946 				return '\\u' + '0000'.substring(a.length) + a;
947 			}) + quote;
948 		}
949 
950 		if (t == 'object') {
951 			if (o.hasOwnProperty && o instanceof Array) {
952 					for (i=0, v = '['; i<o.length; i++)
953 						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
954 
955 					return v + ']';
956 				}
957 
958 				v = '{';
959 
960 				for (name in o) {
961 					if (o.hasOwnProperty(name)) {
962 						v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
963 					}
964 				}
965 
966 				return v + '}';
967 		}
968 
969 		return '' + o;
970 	};
971 
972 	tinymce.util.JSON = {
973 		serialize: serialize,
974 
975 		parse: function(s) {
976 			try {
977 				return eval('(' + s + ')');
978 			} catch (ex) {
979 				// Ignore
980 			}
981 		}
982 
983 		};
984 })();
985 
986 tinymce.create('static tinymce.util.XHR', {
987 	send : function(o) {
988 		var x, t, w = window, c = 0;
989 
990 		function ready() {
991 			if (!o.async || x.readyState == 4 || c++ > 10000) {
992 				if (o.success && c < 10000 && x.status == 200)
993 					o.success.call(o.success_scope, '' + x.responseText, x, o);
994 				else if (o.error)
995 					o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
996 
997 				x = null;
998 			} else
999 				w.setTimeout(ready, 10);
1000 		};
1001 
1002 		// Default settings
1003 		o.scope = o.scope || this;
1004 		o.success_scope = o.success_scope || o.scope;
1005 		o.error_scope = o.error_scope || o.scope;
1006 		o.async = o.async === false ? false : true;
1007 		o.data = o.data || '';
1008 
1009 		function get(s) {
1010 			x = 0;
1011 
1012 			try {
1013 				x = new ActiveXObject(s);
1014 			} catch (ex) {
1015 			}
1016 
1017 			return x;
1018 		};
1019 
1020 		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1021 
1022 		if (x) {
1023 			if (x.overrideMimeType)
1024 				x.overrideMimeType(o.content_type);
1025 
1026 			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1027 
1028 			if (o.content_type)
1029 				x.setRequestHeader('Content-Type', o.content_type);
1030 
1031 			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1032 
1033 			x.send(o.data);
1034 
1035 			// Syncronous request
1036 			if (!o.async)
1037 				return ready();
1038 
1039 			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1040 			t = w.setTimeout(ready, 10);
1041 		}
1042 	}
1043 });
1044 
1045 (function() {
1046 	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1047 
1048 	tinymce.create('tinymce.util.JSONRequest', {
1049 		JSONRequest : function(s) {
1050 			this.settings = extend({
1051 			}, s);
1052 			this.count = 0;
1053 		},
1054 
1055 		send : function(o) {
1056 			var ecb = o.error, scb = o.success;
1057 
1058 			o = extend(this.settings, o);
1059 
1060 			o.success = function(c, x) {
1061 				c = JSON.parse(c);
1062 
1063 				if (typeof(c) == 'undefined') {
1064 					c = {
1065 						error : 'JSON Parse error.'
1066 					};
1067 				}
1068 
1069 				if (c.error)
1070 					ecb.call(o.error_scope || o.scope, c.error, x);
1071 				else
1072 					scb.call(o.success_scope || o.scope, c.result);
1073 			};
1074 
1075 			o.error = function(ty, x) {
1076 				if (ecb)
1077 					ecb.call(o.error_scope || o.scope, ty, x);
1078 			};
1079 
1080 			o.data = JSON.serialize({
1081 				id : o.id || 'c' + (this.count++),
1082 				method : o.method,
1083 				params : o.params
1084 			});
1085 
1086 			// JSON content type for Ruby on rails. Bug: #1883287
1087 			o.content_type = 'application/json';
1088 
1089 			XHR.send(o);
1090 		},
1091 
1092 		'static' : {
1093 			sendRPC : function(o) {
1094 				return new tinymce.util.JSONRequest().send(o);
1095 			}
1096 		}
1097 	});
1098 }());
1099 (function(tinymce){
1100 	tinymce.VK = {
1101 		BACKSPACE: 8,
1102 		DELETE: 46,
1103 		DOWN: 40,
1104 		ENTER: 13,
1105 		LEFT: 37,
1106 		RIGHT: 39,
1107 		SPACEBAR: 32,
1108 		TAB: 9,
1109 		UP: 38,
1110 
1111 		modifierPressed: function (e) {
1112 			return e.shiftKey || e.ctrlKey || e.altKey;
1113 		},
1114 
1115 		metaKeyPressed: function(e) {
1116 			return tinymce.isMac ? e.metaKey : e.ctrlKey;
1117 		}
1118 	};
1119 })(tinymce);
1120 
1121 tinymce.util.Quirks = function(editor) {
1122 	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;
1123 
1124 	function setEditorCommandState(cmd, state) {
1125 		try {
1126 			editor.getDoc().execCommand(cmd, false, state);
1127 		} catch (ex) {
1128 			// Ignore
1129 		}
1130 	}
1131 
1132 	function getDocumentMode() {
1133 		var documentMode = editor.getDoc().documentMode;
1134 
1135 		return documentMode ? documentMode : 6;
1136 	};
1137 
1138 	function cleanupStylesWhenDeleting() {
1139 		function removeMergedFormatSpans(isDelete) {
1140 			var rng, blockElm, node, clonedSpan;
1141 
1142 			rng = selection.getRng();
1143 
1144 			// Find root block
1145 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1146 
1147 			// On delete clone the root span of the next block element
1148 			if (isDelete)
1149 				blockElm = dom.getNext(blockElm, dom.isBlock);
1150 
1151 			// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1152 			if (blockElm) {
1153 				node = blockElm.firstChild;
1154 
1155 				// Ignore empty text nodes
1156 				while (node && node.nodeType == 3 && node.nodeValue.length === 0)
1157 					node = node.nextSibling;
1158 
1159 				if (node && node.nodeName === 'SPAN') {
1160 					clonedSpan = node.cloneNode(false);
1161 				}
1162 			}
1163 
1164 			// Do the backspace/delete action
1165 			editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1166 
1167 			// Find all odd apple-style-spans
1168 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1169 			tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1170 				var bm = selection.getBookmark();
1171 
1172 				if (clonedSpan) {
1173 					dom.replace(clonedSpan.cloneNode(false), span, true);
1174 				} else {
1175 					dom.remove(span, true);
1176 				}
1177 
1178 				// Restore the selection
1179 				selection.moveToBookmark(bm);
1180 			});
1181 		};
1182 
1183 		editor.onKeyDown.add(function(editor, e) {
1184 			var isDelete;
1185 
1186 			isDelete = e.keyCode == DELETE;
1187 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1188 				e.preventDefault();
1189 				removeMergedFormatSpans(isDelete);
1190 			}
1191 		});
1192 
1193 		editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1194 	};
1195 	
1196 	function emptyEditorWhenDeleting() {
1197 		function serializeRng(rng) {
1198 			var body = dom.create("body");
1199 			var contents = rng.cloneContents();
1200 			body.appendChild(contents);
1201 			return selection.serializer.serialize(body, {format: 'html'});
1202 		}
1203 
1204 		function allContentsSelected(rng) {
1205 			var selection = serializeRng(rng);
1206 
1207 			var allRng = dom.createRng();
1208 			allRng.selectNode(editor.getBody());
1209 
1210 			var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection);
1211 			return selection === allSelection;
1212 		}
1213 
1214 		editor.onKeyDown.add(function(editor, e) {
1215 			var keyCode = e.keyCode, isCollapsed;
1216 
1217 			// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1218 			if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
1219 				isCollapsed = editor.selection.isCollapsed();
1220 
1221 				// Selection is collapsed but the editor isn't empty
1222 				if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1223 					return;
1224 				}
1225 
1226 				// IE deletes all contents correctly when everything is selected
1227 				if (tinymce.isIE && !isCollapsed) {
1228 					return;
1229 				}
1230 
1231 				// Selection isn't collapsed but not all the contents is selected
1232 				if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1233 					return;
1234 				}
1235 
1236 				// Manually empty the editor
1237 				editor.setContent('');
1238 				editor.selection.setCursorLocation(editor.getBody(), 0);
1239 				editor.nodeChanged();
1240 			}
1241 		});
1242 	};
1243 
1244 	function selectAll() {
1245 		editor.onKeyDown.add(function(editor, e) {
1246 			if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
1247 				e.preventDefault();
1248 				editor.execCommand('SelectAll');
1249 			}
1250 		});
1251 	};
1252 
1253 	function inputMethodFocus() {
1254 		if (!editor.settings.content_editable) {
1255 			// Case 1 IME doesn't initialize if you focus the document
1256 			dom.bind(editor.getDoc(), 'focusin', function(e) {
1257 				selection.setRng(selection.getRng());
1258 			});
1259 
1260 			// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1261 			dom.bind(editor.getDoc(), 'mousedown', function(e) {
1262 				if (e.target == editor.getDoc().documentElement) {
1263 					editor.getWin().focus();
1264 					selection.setRng(selection.getRng());
1265 				}
1266 			});
1267 		}
1268 	};
1269 
1270 	function removeHrOnBackspace() {
1271 		editor.onKeyDown.add(function(editor, e) {
1272 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1273 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1274 					var node = selection.getNode();
1275 					var previousSibling = node.previousSibling;
1276 
1277 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1278 						dom.remove(previousSibling);
1279 						tinymce.dom.Event.cancel(e);
1280 					}
1281 				}
1282 			}
1283 		})
1284 	}
1285 
1286 	function focusBody() {
1287 		// Fix for a focus bug in FF 3.x where the body element
1288 		// wouldn't get proper focus if the user clicked on the HTML element
1289 		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1290 			editor.onMouseDown.add(function(editor, e) {
1291 				if (e.target.nodeName === "HTML") {
1292 					var body = editor.getBody();
1293 
1294 					// Blur the body it's focused but not correctly focused
1295 					body.blur();
1296 
1297 					// Refocus the body after a little while
1298 					setTimeout(function() {
1299 						body.focus();
1300 					}, 0);
1301 				}
1302 			});
1303 		}
1304 	};
1305 
1306 	function selectControlElements() {
1307 		editor.onClick.add(function(editor, e) {
1308 			e = e.target;
1309 
1310 			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1311 			// WebKit can't even do simple things like selecting an image
1312 			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1313 			if (/^(IMG|HR)$/.test(e.nodeName)) {
1314 				selection.getSel().setBaseAndExtent(e, 0, e, 1);
1315 			}
1316 
1317 			if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1318 				selection.select(e);
1319 			}
1320 
1321 			editor.nodeChanged();
1322 		});
1323 	};
1324 
1325 	function removeStylesWhenDeletingAccrossBlockElements() {
1326 		function getAttributeApplyFunction() {
1327 			var template = dom.getAttribs(selection.getStart().cloneNode(false));
1328 
1329 			return function() {
1330 				var target = selection.getStart();
1331 
1332 				if (target !== editor.getBody()) {
1333 					dom.setAttrib(target, "style", null);
1334 
1335 					tinymce.each(template, function(attr) {
1336 						target.setAttributeNode(attr.cloneNode(true));
1337 					});
1338 				}
1339 			};
1340 		}
1341 
1342 		function isSelectionAcrossElements() {
1343 			return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
1344 		}
1345 
1346 		function blockEvent(editor, e) {
1347 			e.preventDefault();
1348 			return false;
1349 		}
1350 
1351 		editor.onKeyPress.add(function(editor, e) {
1352 			var applyAttributes;
1353 
1354 			if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1355 				applyAttributes = getAttributeApplyFunction();
1356 				editor.getDoc().execCommand('delete', false, null);
1357 				applyAttributes();
1358 				e.preventDefault();
1359 				return false;
1360 			}
1361 		});
1362 
1363 		dom.bind(editor.getDoc(), 'cut', function(e) {
1364 			var applyAttributes;
1365 
1366 			if (isSelectionAcrossElements()) {
1367 				applyAttributes = getAttributeApplyFunction();
1368 				editor.onKeyUp.addToTop(blockEvent);
1369 
1370 				setTimeout(function() {
1371 					applyAttributes();
1372 					editor.onKeyUp.remove(blockEvent);
1373 				}, 0);
1374 			}
1375 		});
1376 	}
1377 
1378 	function selectionChangeNodeChanged() {
1379 		var lastRng, selectionTimer;
1380 
1381 		dom.bind(editor.getDoc(), 'selectionchange', function() {
1382 			if (selectionTimer) {
1383 				clearTimeout(selectionTimer);
1384 				selectionTimer = 0;
1385 			}
1386 
1387 			selectionTimer = window.setTimeout(function() {
1388 				var rng = selection.getRng();
1389 
1390 				// Compare the ranges to see if it was a real change or not
1391 				if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1392 					editor.nodeChanged();
1393 					lastRng = rng;
1394 				}
1395 			}, 50);
1396 		});
1397 	}
1398 
1399 	function ensureBodyHasRoleApplication() {
1400 		document.body.setAttribute("role", "application");
1401 	}
1402 
1403 	function disableBackspaceIntoATable() {
1404 		editor.onKeyDown.add(function(editor, e) {
1405 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1406 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1407 					var previousSibling = selection.getNode().previousSibling;
1408 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1409 						return tinymce.dom.Event.cancel(e);
1410 					}
1411 				}
1412 			}
1413 		})
1414 	}
1415 
1416 	function addNewLinesBeforeBrInPre() {
1417 		// IE8+ rendering mode does the right thing with BR in PRE
1418 		if (getDocumentMode() > 7) {
1419 			return;
1420 		}
1421 
1422 		 // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1423 		 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1424 		setEditorCommandState('RespectVisibilityInDesign', true);
1425 		editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1426 		dom.addClass(editor.getBody(), 'mceHideBrInPre');
1427 
1428 		// Adds a \n before all BR elements in PRE to get them visual
1429 		editor.parser.addNodeFilter('pre', function(nodes, name) {
1430 			var i = nodes.length, brNodes, j, brElm, sibling;
1431 
1432 			while (i--) {
1433 				brNodes = nodes[i].getAll('br');
1434 				j = brNodes.length;
1435 				while (j--) {
1436 					brElm = brNodes[j];
1437 
1438 					// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1439 					sibling = brElm.prev;
1440 					if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1441 						sibling.value += '\n';
1442 					} else {
1443 						brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1444 					}
1445 				}
1446 			}
1447 		});
1448 
1449 		// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1450 		editor.serializer.addNodeFilter('pre', function(nodes, name) {
1451 			var i = nodes.length, brNodes, j, brElm, sibling;
1452 
1453 			while (i--) {
1454 				brNodes = nodes[i].getAll('br');
1455 				j = brNodes.length;
1456 				while (j--) {
1457 					brElm = brNodes[j];
1458 					sibling = brElm.prev;
1459 					if (sibling && sibling.type == 3) {
1460 						sibling.value = sibling.value.replace(/\r?\n$/, '');
1461 					}
1462 				}
1463 			}
1464 		});
1465 	}
1466 
1467 	function removePreSerializedStylesWhenSelectingControls() {
1468 		dom.bind(editor.getBody(), 'mouseup', function(e) {
1469 			var value, node = selection.getNode();
1470 
1471 			// Moved styles to attributes on IMG eements
1472 			if (node.nodeName == 'IMG') {
1473 				// Convert style width to width attribute
1474 				if (value = dom.getStyle(node, 'width')) {
1475 					dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1476 					dom.setStyle(node, 'width', '');
1477 				}
1478 
1479 				// Convert style height to height attribute
1480 				if (value = dom.getStyle(node, 'height')) {
1481 					dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1482 					dom.setStyle(node, 'height', '');
1483 				}
1484 			}
1485 		});
1486 	}
1487 
1488 	function keepInlineElementOnDeleteBackspace() {
1489 		editor.onKeyDown.add(function(editor, e) {
1490 			var isDelete, rng, container, offset, brElm, sibling, collapsed;
1491 
1492 			isDelete = e.keyCode == DELETE;
1493 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1494 				rng = selection.getRng();
1495 				container = rng.startContainer;
1496 				offset = rng.startOffset;
1497 				collapsed = rng.collapsed;
1498 
1499 				// Override delete if the start container is a text node and is at the beginning of text or
1500 				// just before/after the last character to be deleted in collapsed mode
1501 				if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1502 					nonEmptyElements = editor.schema.getNonEmptyElements();
1503 
1504 					// Prevent default logic since it's broken
1505 					e.preventDefault();
1506 
1507 					// Insert a BR before the text node this will prevent the containing element from being deleted/converted
1508 					brElm = dom.create('br', {id: '__tmp'});
1509 					container.parentNode.insertBefore(brElm, container);
1510 
1511 					// Do the browser delete
1512 					editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1513 
1514 					// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1515 					container = selection.getRng().startContainer;
1516 					sibling = container.previousSibling;
1517 					if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1518 						dom.remove(sibling);
1519 					}
1520 
1521 					// Remove the temp element we inserted
1522 					dom.remove('__tmp');
1523 				}
1524 			}
1525 		});
1526 	}
1527 
1528 	function removeBlockQuoteOnBackSpace() {
1529 		// Add block quote deletion handler
1530 		editor.onKeyDown.add(function(editor, e) {
1531 			var rng, container, offset, root, parent;
1532 
1533 			if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
1534 				return;
1535 			}
1536 
1537 			rng = selection.getRng();
1538 			container = rng.startContainer;
1539 			offset = rng.startOffset;
1540 			root = dom.getRoot();
1541 			parent = container;
1542 
1543 			if (!rng.collapsed || offset !== 0) {
1544 				return;
1545 			}
1546 
1547 			while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1548 				parent = parent.parentNode;
1549 			}
1550 
1551 			// Is the cursor at the beginning of a blockquote?
1552 			if (parent.tagName === 'BLOCKQUOTE') {
1553 				// Remove the blockquote
1554 				editor.formatter.toggle('blockquote', null, parent);
1555 
1556 				// Move the caret to the beginning of container
1557 				rng.setStart(container, 0);
1558 				rng.setEnd(container, 0);
1559 				selection.setRng(rng);
1560 				selection.collapse(false);
1561 			}
1562 		});
1563 	};
1564 
1565 	function setGeckoEditingOptions() {
1566 		function setOpts() {
1567 			editor._refreshContentEditable();
1568 
1569 			setEditorCommandState("StyleWithCSS", false);
1570 			setEditorCommandState("enableInlineTableEditing", false);
1571 
1572 			if (!settings.object_resizing) {
1573 				setEditorCommandState("enableObjectResizing", false);
1574 			}
1575 		};
1576 
1577 		if (!settings.readonly) {
1578 			editor.onBeforeExecCommand.add(setOpts);
1579 			editor.onMouseDown.add(setOpts);
1580 		}
1581 	};
1582 
1583 	function addBrAfterLastLinks() {
1584 		function fixLinks(editor, o) {
1585 			tinymce.each(dom.select('a'), function(node) {
1586 				var parentNode = node.parentNode, root = dom.getRoot();
1587 
1588 				if (parentNode.lastChild === node) {
1589 					while (parentNode && !dom.isBlock(parentNode)) {
1590 						if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1591 							return;
1592 						}
1593 
1594 						parentNode = parentNode.parentNode;
1595 					}
1596 
1597 					dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1598 				}
1599 			});
1600 		};
1601 
1602 		editor.onExecCommand.add(function(editor, cmd) {
1603 			if (cmd === 'CreateLink') {
1604 				fixLinks(editor);
1605 			}
1606 		});
1607 
1608 		editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1609 	};
1610 
1611 	function setDefaultBlockType() {
1612 		if (settings.forced_root_block) {
1613 			editor.onInit.add(function() {
1614 				setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1615 			});
1616 		}
1617 	}
1618 
1619 	function removeGhostSelection() {
1620 		function repaint(sender, args) {
1621 			if (!sender || !args.initial) {
1622 				editor.execCommand('mceRepaint');
1623 			}
1624 		};
1625 
1626 		editor.onUndo.add(repaint);
1627 		editor.onRedo.add(repaint);
1628 		editor.onSetContent.add(repaint);
1629 	};
1630 
1631 	function deleteControlItemOnBackSpace() {
1632 		editor.onKeyDown.add(function(editor, e) {
1633 			var rng;
1634 
1635 			if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) {
1636 				rng = editor.getDoc().selection.createRange();
1637 				if (rng && rng.item) {
1638 					e.preventDefault();
1639 					editor.undoManager.beforeChange();
1640 					dom.remove(rng.item(0));
1641 					editor.undoManager.add();
1642 				}
1643 			}
1644 		});
1645 	};
1646 
1647 	function renderEmptyBlocksFix() {
1648 		var emptyBlocksCSS;
1649 
1650 		// IE10+
1651 		if (getDocumentMode() >= 10) {
1652 			emptyBlocksCSS = '';
1653 			tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1654 				emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1655 			});
1656 
1657 			editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1658 		}
1659 	};
1660 
1661 	function fakeImageResize() {
1662 		var mouseDownImg, startX, startY, startW, startH;
1663 
1664 		if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1665 			return;
1666 		}
1667 
1668 		editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}');
1669 
1670 		function resizeImage(e) {
1671 			var deltaX, deltaY, ratio, width, height;
1672 
1673 			if (mouseDownImg) {
1674 				deltaX = e.screenX - startX;
1675 				deltaY = e.screenY - startY;
1676 				ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH);
1677 
1678 				// Only update styles if the user draged one pixel or more
1679 				if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
1680 					// Constrain proportions
1681 					width = Math.round(startW * ratio);
1682 					height = Math.round(startH * ratio);
1683 
1684 					// Resize by using style or attribute
1685 					if (mouseDownImg.style.width) {
1686 						dom.setStyle(mouseDownImg, 'width', width);
1687 					} else {
1688 						dom.setAttrib(mouseDownImg, 'width', width);
1689 					}
1690 
1691 					// Resize by using style or attribute
1692 					if (mouseDownImg.style.height) {
1693 						dom.setStyle(mouseDownImg, 'height', height);
1694 					} else {
1695 						dom.setAttrib(mouseDownImg, 'height', height);
1696 					}
1697 
1698 					if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) {
1699 						dom.addClass(editor.getBody(), 'mceResizeImages');
1700 					}
1701 				}
1702 			}
1703 		};
1704 
1705 		editor.onMouseDown.add(function(editor, e) {
1706 			var target = e.target;
1707 
1708 			if (target.nodeName == "IMG") {
1709 				mouseDownImg = target;
1710 				startX = e.screenX;
1711 				startY = e.screenY;
1712 				startW = mouseDownImg.clientWidth;
1713 				startH = mouseDownImg.clientHeight;
1714 				dom.bind(editor.getDoc(), 'mousemove', resizeImage);
1715 				e.preventDefault();
1716 			}
1717 		});
1718 
1719 		// Unbind events on node change and restore resize cursor
1720 		editor.onNodeChange.add(function() {
1721 			if (mouseDownImg) {
1722 				mouseDownImg = null;
1723 				dom.unbind(editor.getDoc(), 'mousemove', resizeImage);
1724 			}
1725 
1726 			if (selection.getNode().nodeName == "IMG") {
1727 				dom.addClass(editor.getBody(), 'mceResizeImages');
1728 			} else {
1729 				dom.removeClass(editor.getBody(), 'mceResizeImages');
1730 			}
1731 		});
1732 	};
1733 
1734 	// All browsers
1735 	disableBackspaceIntoATable();
1736 	removeBlockQuoteOnBackSpace();
1737 	emptyEditorWhenDeleting();
1738 
1739 	// WebKit
1740 	if (tinymce.isWebKit) {
1741 		keepInlineElementOnDeleteBackspace();
1742 		cleanupStylesWhenDeleting();
1743 		inputMethodFocus();
1744 		selectControlElements();
1745 		setDefaultBlockType();
1746 
1747 		// iOS
1748 		if (tinymce.isIDevice) {
1749 			selectionChangeNodeChanged();
1750 		} else {
1751 			fakeImageResize();
1752 			selectAll();
1753 		}
1754 	}
1755 
1756 	// IE
1757 	if (tinymce.isIE) {
1758 		removeHrOnBackspace();
1759 		ensureBodyHasRoleApplication();
1760 		addNewLinesBeforeBrInPre();
1761 		removePreSerializedStylesWhenSelectingControls();
1762 		deleteControlItemOnBackSpace();
1763 		renderEmptyBlocksFix();
1764 	}
1765 
1766 	// Gecko
1767 	if (tinymce.isGecko) {
1768 		removeHrOnBackspace();
1769 		focusBody();
1770 		removeStylesWhenDeletingAccrossBlockElements();
1771 		setGeckoEditingOptions();
1772 		addBrAfterLastLinks();
1773 		removeGhostSelection();
1774 	}
1775 };
1776 (function(tinymce) {
1777 	var namedEntities, baseEntities, reverseEntities,
1778 		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1779 		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1780 		rawCharsRegExp = /[<>&\"\']/g,
1781 		entityRegExp = /&(#x|#)?([\w]+);/g,
1782 		asciiMap = {
1783 				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1784 				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1785 				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1786 				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1787 				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1788 		};
1789 
1790 	// Raw entities
1791 	baseEntities = {
1792 		'\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
1793 		"'" : ''',
1794 		'<' : '<',
1795 		'>' : '>',
1796 		'&' : '&'
1797 	};
1798 
1799 	// Reverse lookup table for raw entities
1800 	reverseEntities = {
1801 		'<' : '<',
1802 		'>' : '>',
1803 		'&' : '&',
1804 		'"' : '"',
1805 		''' : "'"
1806 	};
1807 
1808 	// Decodes text by using the browser
1809 	function nativeDecode(text) {
1810 		var elm;
1811 
1812 		elm = document.createElement("div");
1813 		elm.innerHTML = text;
1814 
1815 		return elm.textContent || elm.innerText || text;
1816 	};
1817 
1818 	// Build a two way lookup table for the entities
1819 	function buildEntitiesLookup(items, radix) {
1820 		var i, chr, entity, lookup = {};
1821 
1822 		if (items) {
1823 			items = items.split(',');
1824 			radix = radix || 10;
1825 
1826 			// Build entities lookup table
1827 			for (i = 0; i < items.length; i += 2) {
1828 				chr = String.fromCharCode(parseInt(items[i], radix));
1829 
1830 				// Only add non base entities
1831 				if (!baseEntities[chr]) {
1832 					entity = '&' + items[i + 1] + ';';
1833 					lookup[chr] = entity;
1834 					lookup[entity] = chr;
1835 				}
1836 			}
1837 
1838 			return lookup;
1839 		}
1840 	};
1841 
1842 	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
1843 	namedEntities = buildEntitiesLookup(
1844 		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1845 		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1846 		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1847 		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1848 		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1849 		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1850 		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1851 		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1852 		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1853 		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1854 		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1855 		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1856 		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1857 		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1858 		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1859 		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1860 		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1861 		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1862 		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1863 		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1864 		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1865 		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1866 		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1867 		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1868 		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
1869 
1870 	tinymce.html = tinymce.html || {};
1871 
1872 	tinymce.html.Entities = {
1873 		encodeRaw : function(text, attr) {
1874 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1875 				return baseEntities[chr] || chr;
1876 			});
1877 		},
1878 
1879 		encodeAllRaw : function(text) {
1880 			return ('' + text).replace(rawCharsRegExp, function(chr) {
1881 				return baseEntities[chr] || chr;
1882 			});
1883 		},
1884 
1885 		encodeNumeric : function(text, attr) {
1886 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1887 				// Multi byte sequence convert it to a single entity
1888 				if (chr.length > 1)
1889 					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1890 
1891 				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1892 			});
1893 		},
1894 
1895 		encodeNamed : function(text, attr, entities) {
1896 			entities = entities || namedEntities;
1897 
1898 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1899 				return baseEntities[chr] || entities[chr] || chr;
1900 			});
1901 		},
1902 
1903 		getEncodeFunc : function(name, entities) {
1904 			var Entities = tinymce.html.Entities;
1905 
1906 			entities = buildEntitiesLookup(entities) || namedEntities;
1907 
1908 			function encodeNamedAndNumeric(text, attr) {
1909 				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1910 					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1911 				});
1912 			};
1913 
1914 			function encodeCustomNamed(text, attr) {
1915 				return Entities.encodeNamed(text, attr, entities);
1916 			};
1917 
1918 			// Replace + with , to be compatible with previous TinyMCE versions
1919 			name = tinymce.makeMap(name.replace(/\+/g, ','));
1920 
1921 			// Named and numeric encoder
1922 			if (name.named && name.numeric)
1923 				return encodeNamedAndNumeric;
1924 
1925 			// Named encoder
1926 			if (name.named) {
1927 				// Custom names
1928 				if (entities)
1929 					return encodeCustomNamed;
1930 
1931 				return Entities.encodeNamed;
1932 			}
1933 
1934 			// Numeric
1935 			if (name.numeric)
1936 				return Entities.encodeNumeric;
1937 
1938 			// Raw encoder
1939 			return Entities.encodeRaw;
1940 		},
1941 
1942 		decode : function(text) {
1943 			return text.replace(entityRegExp, function(all, numeric, value) {
1944 				if (numeric) {
1945 					value = parseInt(value, numeric.length === 2 ? 16 : 10);
1946 
1947 					// Support upper UTF
1948 					if (value > 0xFFFF) {
1949 						value -= 0x10000;
1950 
1951 						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1952 					} else
1953 						return asciiMap[value] || String.fromCharCode(value);
1954 				}
1955 
1956 				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1957 			});
1958 		}
1959 	};
1960 })(tinymce);
1961 
1962 tinymce.html.Styles = function(settings, schema) {
1963 	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1964 		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1965 		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1966 		trimRightRegExp = /\s+$/,
1967 		urlColorRegExp = /rgb/,
1968 		undef, i, encodingLookup = {}, encodingItems;
1969 
1970 	settings = settings || {};
1971 
1972 	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1973 	for (i = 0; i < encodingItems.length; i++) {
1974 		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1975 		encodingLookup['\uFEFF' + i] = encodingItems[i];
1976 	}
1977 
1978 	function toHex(match, r, g, b) {
1979 		function hex(val) {
1980 			val = parseInt(val).toString(16);
1981 
1982 			return val.length > 1 ? val : '0' + val; // 0 -> 00
1983 		};
1984 
1985 		return '#' + hex(r) + hex(g) + hex(b);
1986 	};
1987 
1988 	return {
1989 		toHex : function(color) {
1990 			return color.replace(rgbRegExp, toHex);
1991 		},
1992 
1993 		parse : function(css) {
1994 			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1995 
1996 			function compress(prefix, suffix) {
1997 				var top, right, bottom, left;
1998 
1999 				// Get values and check it it needs compressing
2000 				top = styles[prefix + '-top' + suffix];
2001 				if (!top)
2002 					return;
2003 
2004 				right = styles[prefix + '-right' + suffix];
2005 				if (top != right)
2006 					return;
2007 
2008 				bottom = styles[prefix + '-bottom' + suffix];
2009 				if (right != bottom)
2010 					return;
2011 
2012 				left = styles[prefix + '-left' + suffix];
2013 				if (bottom != left)
2014 					return;
2015 
2016 				// Compress
2017 				styles[prefix + suffix] = left;
2018 				delete styles[prefix + '-top' + suffix];
2019 				delete styles[prefix + '-right' + suffix];
2020 				delete styles[prefix + '-bottom' + suffix];
2021 				delete styles[prefix + '-left' + suffix];
2022 			};
2023 
2024 			function canCompress(key) {
2025 				var value = styles[key], i;
2026 
2027 				if (!value || value.indexOf(' ') < 0)
2028 					return;
2029 
2030 				value = value.split(' ');
2031 				i = value.length;
2032 				while (i--) {
2033 					if (value[i] !== value[0])
2034 						return false;
2035 				}
2036 
2037 				styles[key] = value[0];
2038 
2039 				return true;
2040 			};
2041 
2042 			function compress2(target, a, b, c) {
2043 				if (!canCompress(a))
2044 					return;
2045 
2046 				if (!canCompress(b))
2047 					return;
2048 
2049 				if (!canCompress(c))
2050 					return;
2051 
2052 				// Compress
2053 				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2054 				delete styles[a];
2055 				delete styles[b];
2056 				delete styles[c];
2057 			};
2058 
2059 			// Encodes the specified string by replacing all \" \' ; : with _<num>
2060 			function encode(str) {
2061 				isEncoded = true;
2062 
2063 				return encodingLookup[str];
2064 			};
2065 
2066 			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2067 			// It will also decode the \" \' if keep_slashes is set to fale or omitted
2068 			function decode(str, keep_slashes) {
2069 				if (isEncoded) {
2070 					str = str.replace(/\uFEFF[0-9]/g, function(str) {
2071 						return encodingLookup[str];
2072 					});
2073 				}
2074 
2075 				if (!keep_slashes)
2076 					str = str.replace(/\\([\'\";:])/g, "$1");
2077 
2078 				return str;
2079 			};
2080 
2081 			function processUrl(match, url, url2, url3, str, str2) {
2082 				str = str || str2;
2083 
2084 				if (str) {
2085 					str = decode(str);
2086 
2087 					// Force strings into single quote format
2088 					return "'" + str.replace(/\'/g, "\\'") + "'";
2089 				}
2090 
2091 				url = decode(url || url2 || url3);
2092 
2093 				// Convert the URL to relative/absolute depending on config
2094 				if (urlConverter)
2095 					url = urlConverter.call(urlConverterScope, url, 'style');
2096 
2097 				// Output new URL format
2098 				return "url('" + url.replace(/\'/g, "\\'") + "')";
2099 			};
2100 
2101 			if (css) {
2102 				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2103 				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2104 					return str.replace(/[;:]/g, encode);
2105 				});
2106 
2107 				// Parse styles
2108 				while (matches = styleRegExp.exec(css)) {
2109 					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2110 					value = matches[2].replace(trimRightRegExp, '');
2111 
2112 					if (name && value.length > 0) {
2113 						// Opera will produce 700 instead of bold in their style values
2114 						if (name === 'font-weight' && value === '700')
2115 							value = 'bold';
2116 						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2117 							value = value.toLowerCase();		
2118 
2119 						// Convert RGB colors to HEX
2120 						value = value.replace(rgbRegExp, toHex);
2121 
2122 						// Convert URLs and force them into url('value') format
2123 						value = value.replace(urlOrStrRegExp, processUrl);
2124 						styles[name] = isEncoded ? decode(value, true) : value;
2125 					}
2126 
2127 					styleRegExp.lastIndex = matches.index + matches[0].length;
2128 				}
2129 
2130 				// Compress the styles to reduce it's size for example IE will expand styles
2131 				compress("border", "");
2132 				compress("border", "-width");
2133 				compress("border", "-color");
2134 				compress("border", "-style");
2135 				compress("padding", "");
2136 				compress("margin", "");
2137 				compress2('border', 'border-width', 'border-style', 'border-color');
2138 
2139 				// Remove pointless border, IE produces these
2140 				if (styles.border === 'medium none')
2141 					delete styles.border;
2142 			}
2143 
2144 			return styles;
2145 		},
2146 
2147 		serialize : function(styles, element_name) {
2148 			var css = '', name, value;
2149 
2150 			function serializeStyles(name) {
2151 				var styleList, i, l, value;
2152 
2153 				styleList = schema.styles[name];
2154 				if (styleList) {
2155 					for (i = 0, l = styleList.length; i < l; i++) {
2156 						name = styleList[i];
2157 						value = styles[name];
2158 
2159 						if (value !== undef && value.length > 0)
2160 							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2161 					}
2162 				}
2163 			};
2164 
2165 			// Serialize styles according to schema
2166 			if (element_name && schema && schema.styles) {
2167 				// Serialize global styles and element specific styles
2168 				serializeStyles('*');
2169 				serializeStyles(element_name);
2170 			} else {
2171 				// Output the styles in the order they are inside the object
2172 				for (name in styles) {
2173 					value = styles[name];
2174 
2175 					if (value !== undef && value.length > 0)
2176 						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2177 				}
2178 			}
2179 
2180 			return css;
2181 		}
2182 	};
2183 };
2184 
2185 (function(tinymce) {
2186 	var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2187 
2188 	function split(str, delim) {
2189 		return str.split(delim || ',');
2190 	};
2191 
2192 	function unpack(lookup, data) {
2193 		var key, elements = {};
2194 
2195 		function replace(value) {
2196 			return value.replace(/[A-Z]+/g, function(key) {
2197 				return replace(lookup[key]);
2198 			});
2199 		};
2200 
2201 		// Unpack lookup
2202 		for (key in lookup) {
2203 			if (lookup.hasOwnProperty(key))
2204 				lookup[key] = replace(lookup[key]);
2205 		}
2206 
2207 		// Unpack and parse data into object map
2208 		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2209 			attributes = split(attributes, '|');
2210 
2211 			elements[name] = {
2212 				attributes : makeMap(attributes),
2213 				attributesOrder : attributes,
2214 				children : makeMap(children, '|', {'#comment' : {}})
2215 			}
2216 		});
2217 
2218 		return elements;
2219 	};
2220 
2221 	function getHTML5() {
2222 		var html5 = mapCache.html5;
2223 
2224 		if (!html5) {
2225 			html5 = mapCache.html5 = unpack({
2226 					A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2227 					B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2228 						'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2229 					C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2230 						'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2231 						'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2232 				}, 'html[A|manifest][body|head]' +
2233 					'head[A][base|command|link|meta|noscript|script|style|title]' +
2234 					'title[A][#]' +
2235 					'base[A|href|target][]' +
2236 					'link[A|href|rel|media|type|sizes][]' +
2237 					'meta[A|http-equiv|name|content|charset][]' +
2238 					'style[A|type|media|scoped][#]' +
2239 					'script[A|charset|type|src|defer|async][#]' +
2240 					'noscript[A][C]' +
2241 					'body[A][C]' +
2242 					'section[A][C]' +
2243 					'nav[A][C]' +
2244 					'article[A][C]' +
2245 					'aside[A][C]' +
2246 					'h1[A][B]' +
2247 					'h2[A][B]' +
2248 					'h3[A][B]' +
2249 					'h4[A][B]' +
2250 					'h5[A][B]' +
2251 					'h6[A][B]' +
2252 					'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2253 					'header[A][C]' +
2254 					'footer[A][C]' +
2255 					'address[A][C]' +
2256 					'p[A][B]' +
2257 					'br[A][]' +
2258 					'pre[A][B]' +
2259 					'dialog[A][dd|dt]' +
2260 					'blockquote[A|cite][C]' +
2261 					'ol[A|start|reversed][li]' +
2262 					'ul[A][li]' +
2263 					'li[A|value][C]' +
2264 					'dl[A][dd|dt]' +
2265 					'dt[A][B]' +
2266 					'dd[A][C]' +
2267 					'a[A|href|target|ping|rel|media|type][B]' +
2268 					'em[A][B]' +
2269 					'strong[A][B]' +
2270 					'small[A][B]' +
2271 					'cite[A][B]' +
2272 					'q[A|cite][B]' +
2273 					'dfn[A][B]' +
2274 					'abbr[A][B]' +
2275 					'code[A][B]' +
2276 					'var[A][B]' +
2277 					'samp[A][B]' +
2278 					'kbd[A][B]' +
2279 					'sub[A][B]' +
2280 					'sup[A][B]' +
2281 					'i[A][B]' +
2282 					'b[A][B]' +
2283 					'mark[A][B]' +
2284 					'progress[A|value|max][B]' +
2285 					'meter[A|value|min|max|low|high|optimum][B]' +
2286 					'time[A|datetime][B]' +
2287 					'ruby[A][B|rt|rp]' +
2288 					'rt[A][B]' +
2289 					'rp[A][B]' +
2290 					'bdo[A][B]' +
2291 					'span[A][B]' +
2292 					'ins[A|cite|datetime][B]' +
2293 					'del[A|cite|datetime][B]' +
2294 					'figure[A][C|legend|figcaption]' +
2295 					'figcaption[A][C]' +
2296 					'img[A|alt|src|height|width|usemap|ismap][]' +
2297 					'iframe[A|name|src|height|width|sandbox|seamless][]' +
2298 					'embed[A|src|height|width|type][]' +
2299 					'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2300 					'param[A|name|value][]' +
2301 					'details[A|open][C|legend]' +
2302 					'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2303 					'menu[A|type|label][C|li]' +
2304 					'legend[A][C|B]' +
2305 					'div[A][C]' +
2306 					'source[A|src|type|media][]' +
2307 					'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2308 					'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2309 					'hr[A][]' +
2310 					'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2311 					'fieldset[A|disabled|form|name][C|legend]' +
2312 					'label[A|form|for][B]' +
2313 					'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2314 						'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2315 					'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2316 					'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2317 					'datalist[A][B|option]' +
2318 					'optgroup[A|disabled|label][option]' +
2319 					'option[A|disabled|selected|label|value][]' +
2320 					'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2321 					'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2322 					'output[A|for|form|name][B]' +
2323 					'canvas[A|width|height][]' +
2324 					'map[A|name][B|C]' +
2325 					'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2326 					'mathml[A][]' +
2327 					'svg[A][]' +
2328 					'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2329 					'caption[A][C]' +
2330 					'colgroup[A|span][col]' +
2331 					'col[A|span][]' +
2332 					'thead[A][tr]' +
2333 					'tfoot[A][tr]' +
2334 					'tbody[A][tr]' +
2335 					'tr[A][th|td]' +
2336 					'th[A|headers|rowspan|colspan|scope][B]' +
2337 					'td[A|headers|rowspan|colspan][C]' +
2338 					'wbr[A][]'
2339 			);
2340 		}
2341 
2342 		return html5;
2343 	};
2344 
2345 	function getHTML4() {
2346 		var html4 = mapCache.html4;
2347 
2348 		if (!html4) {
2349 			// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2350 			html4 = mapCache.html4 = unpack({
2351 				Z : 'H|K|N|O|P',
2352 				Y : 'X|form|R|Q',
2353 				ZG : 'E|span|width|align|char|charoff|valign',
2354 				X : 'p|T|div|U|W|isindex|fieldset|table',
2355 				ZF : 'E|align|char|charoff|valign',
2356 				W : 'pre|hr|blockquote|address|center|noframes',
2357 				ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2358 				ZD : '[E][S]',
2359 				U : 'ul|ol|dl|menu|dir',
2360 				ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2361 				T : 'h1|h2|h3|h4|h5|h6',
2362 				ZB : 'X|S|Q',
2363 				S : 'R|P',
2364 				ZA : 'a|G|J|M|O|P',
2365 				R : 'a|H|K|N|O',
2366 				Q : 'noscript|P',
2367 				P : 'ins|del|script',
2368 				O : 'input|select|textarea|label|button',
2369 				N : 'M|L',
2370 				M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2371 				L : 'sub|sup',
2372 				K : 'J|I',
2373 				J : 'tt|i|b|u|s|strike',
2374 				I : 'big|small|font|basefont',
2375 				H : 'G|F',
2376 				G : 'br|span|bdo',
2377 				F : 'object|applet|img|map|iframe',
2378 				E : 'A|B|C',
2379 				D : 'accesskey|tabindex|onfocus|onblur',
2380 				C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2381 				B : 'lang|xml:lang|dir',
2382 				A : 'id|class|style|title'
2383 			}, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2384 				'style[B|id|type|media|title|xml:space][]' + 
2385 				'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2386 				'param[id|name|value|valuetype|type][]' + 
2387 				'p[E|align][#|S]' + 
2388 				'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2389 				'br[A|clear][]' + 
2390 				'span[E][#|S]' + 
2391 				'bdo[A|C|B][#|S]' + 
2392 				'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2393 				'h1[E|align][#|S]' + 
2394 				'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2395 				'map[B|C|A|name][X|form|Q|area]' + 
2396 				'h2[E|align][#|S]' + 
2397 				'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2398 				'h3[E|align][#|S]' + 
2399 				'tt[E][#|S]' + 
2400 				'i[E][#|S]' + 
2401 				'b[E][#|S]' + 
2402 				'u[E][#|S]' + 
2403 				's[E][#|S]' + 
2404 				'strike[E][#|S]' + 
2405 				'big[E][#|S]' + 
2406 				'small[E][#|S]' + 
2407 				'font[A|B|size|color|face][#|S]' + 
2408 				'basefont[id|size|color|face][]' + 
2409 				'em[E][#|S]' + 
2410 				'strong[E][#|S]' + 
2411 				'dfn[E][#|S]' + 
2412 				'code[E][#|S]' + 
2413 				'q[E|cite][#|S]' + 
2414 				'samp[E][#|S]' + 
2415 				'kbd[E][#|S]' + 
2416 				'var[E][#|S]' + 
2417 				'cite[E][#|S]' + 
2418 				'abbr[E][#|S]' + 
2419 				'acronym[E][#|S]' + 
2420 				'sub[E][#|S]' + 
2421 				'sup[E][#|S]' + 
2422 				'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2423 				'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2424 				'optgroup[E|disabled|label][option]' + 
2425 				'option[E|selected|disabled|label|value][]' + 
2426 				'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2427 				'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2428 				'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2429 				'h4[E|align][#|S]' + 
2430 				'ins[E|cite|datetime][#|Y]' + 
2431 				'h5[E|align][#|S]' + 
2432 				'del[E|cite|datetime][#|Y]' + 
2433 				'h6[E|align][#|S]' + 
2434 				'div[E|align][#|Y]' + 
2435 				'ul[E|type|compact][li]' + 
2436 				'li[E|type|value][#|Y]' + 
2437 				'ol[E|type|compact|start][li]' + 
2438 				'dl[E|compact][dt|dd]' + 
2439 				'dt[E][#|S]' + 
2440 				'dd[E][#|Y]' + 
2441 				'menu[E|compact][li]' + 
2442 				'dir[E|compact][li]' + 
2443 				'pre[E|width|xml:space][#|ZA]' + 
2444 				'hr[E|align|noshade|size|width][]' + 
2445 				'blockquote[E|cite][#|Y]' + 
2446 				'address[E][#|S|p]' + 
2447 				'center[E][#|Y]' + 
2448 				'noframes[E][#|Y]' + 
2449 				'isindex[A|B|prompt][]' + 
2450 				'fieldset[E][#|legend|Y]' + 
2451 				'legend[E|accesskey|align][#|S]' + 
2452 				'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2453 				'caption[E|align][#|S]' + 
2454 				'col[ZG][]' + 
2455 				'colgroup[ZG][col]' + 
2456 				'thead[ZF][tr]' + 
2457 				'tr[ZF|bgcolor][th|td]' + 
2458 				'th[E|ZE][#|Y]' + 
2459 				'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2460 				'noscript[E][#|Y]' + 
2461 				'td[E|ZE][#|Y]' + 
2462 				'tfoot[ZF][tr]' + 
2463 				'tbody[ZF][tr]' + 
2464 				'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2465 				'base[id|href|target][]' + 
2466 				'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2467 			);
2468 		}
2469 
2470 		return html4;
2471 	};
2472 
2473 	tinymce.html.Schema = function(settings) {
2474 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2475 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2476 
2477 		// Creates an lookup table map object for the specified option or the default value
2478 		function createLookupTable(option, default_value, extend) {
2479 			var value = settings[option];
2480 
2481 			if (!value) {
2482 				// Get cached default map or make it if needed
2483 				value = mapCache[option];
2484 
2485 				if (!value) {
2486 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2487 					value = tinymce.extend(value, extend);
2488 
2489 					mapCache[option] = value;
2490 				}
2491 			} else {
2492 				// Create custom map
2493 				value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2494 			}
2495 
2496 			return value;
2497 		};
2498 
2499 		settings = settings || {};
2500 		schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2501 
2502 		// Allow all elements and attributes if verify_html is set to false
2503 		if (settings.verify_html === false)
2504 			settings.valid_elements = '*[*]';
2505 
2506 		// Build styles list
2507 		if (settings.valid_styles) {
2508 			validStyles = {};
2509 
2510 			// Convert styles into a rule list
2511 			each(settings.valid_styles, function(value, key) {
2512 				validStyles[key] = tinymce.explode(value);
2513 			});
2514 		}
2515 
2516 		// Setup map objects
2517 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
2518 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2519 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2520 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2521 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
2522 		blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 
2523 						'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 
2524 						'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
2525 
2526 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2527 		function patternToRegExp(str) {
2528 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2529 		};
2530 
2531 		// Parses the specified valid_elements string and adds to the current rules
2532 		// This function is a bit hard to read since it's heavily optimized for speed
2533 		function addValidElements(valid_elements) {
2534 			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2535 				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2536 				elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2537 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2538 				hasPatternsRegExp = /[*?+]/;
2539 
2540 			if (valid_elements) {
2541 				// Split valid elements into an array with rules
2542 				valid_elements = split(valid_elements);
2543 
2544 				if (elements['@']) {
2545 					globalAttributes = elements['@'].attributes;
2546 					globalAttributesOrder = elements['@'].attributesOrder;
2547 				}
2548 
2549 				// Loop all rules
2550 				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2551 					// Parse element rule
2552 					matches = elementRuleRegExp.exec(valid_elements[ei]);
2553 					if (matches) {
2554 						// Setup local names for matches
2555 						prefix = matches[1];
2556 						elementName = matches[2];
2557 						outputName = matches[3];
2558 						attrData = matches[4];
2559 
2560 						// Create new attributes and attributesOrder
2561 						attributes = {};
2562 						attributesOrder = [];
2563 
2564 						// Create the new element
2565 						element = {
2566 							attributes : attributes,
2567 							attributesOrder : attributesOrder
2568 						};
2569 
2570 						// Padd empty elements prefix
2571 						if (prefix === '#')
2572 							element.paddEmpty = true;
2573 
2574 						// Remove empty elements prefix
2575 						if (prefix === '-')
2576 							element.removeEmpty = true;
2577 
2578 						// Copy attributes from global rule into current rule
2579 						if (globalAttributes) {
2580 							for (key in globalAttributes)
2581 								attributes[key] = globalAttributes[key];
2582 
2583 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2584 						}
2585 
2586 						// Attributes defined
2587 						if (attrData) {
2588 							attrData = split(attrData, '|');
2589 							for (ai = 0, al = attrData.length; ai < al; ai++) {
2590 								matches = attrRuleRegExp.exec(attrData[ai]);
2591 								if (matches) {
2592 									attr = {};
2593 									attrType = matches[1];
2594 									attrName = matches[2].replace(/::/g, ':');
2595 									prefix = matches[3];
2596 									value = matches[4];
2597 
2598 									// Required
2599 									if (attrType === '!') {
2600 										element.attributesRequired = element.attributesRequired || [];
2601 										element.attributesRequired.push(attrName);
2602 										attr.required = true;
2603 									}
2604 
2605 									// Denied from global
2606 									if (attrType === '-') {
2607 										delete attributes[attrName];
2608 										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2609 										continue;
2610 									}
2611 
2612 									// Default value
2613 									if (prefix) {
2614 										// Default value
2615 										if (prefix === '=') {
2616 											element.attributesDefault = element.attributesDefault || [];
2617 											element.attributesDefault.push({name: attrName, value: value});
2618 											attr.defaultValue = value;
2619 										}
2620 
2621 										// Forced value
2622 										if (prefix === ':') {
2623 											element.attributesForced = element.attributesForced || [];
2624 											element.attributesForced.push({name: attrName, value: value});
2625 											attr.forcedValue = value;
2626 										}
2627 
2628 										// Required values
2629 										if (prefix === '<')
2630 											attr.validValues = makeMap(value, '?');
2631 									}
2632 
2633 									// Check for attribute patterns
2634 									if (hasPatternsRegExp.test(attrName)) {
2635 										element.attributePatterns = element.attributePatterns || [];
2636 										attr.pattern = patternToRegExp(attrName);
2637 										element.attributePatterns.push(attr);
2638 									} else {
2639 										// Add attribute to order list if it doesn't already exist
2640 										if (!attributes[attrName])
2641 											attributesOrder.push(attrName);
2642 
2643 										attributes[attrName] = attr;
2644 									}
2645 								}
2646 							}
2647 						}
2648 
2649 						// Global rule, store away these for later usage
2650 						if (!globalAttributes && elementName == '@') {
2651 							globalAttributes = attributes;
2652 							globalAttributesOrder = attributesOrder;
2653 						}
2654 
2655 						// Handle substitute elements such as b/strong
2656 						if (outputName) {
2657 							element.outputName = elementName;
2658 							elements[outputName] = element;
2659 						}
2660 
2661 						// Add pattern or exact element
2662 						if (hasPatternsRegExp.test(elementName)) {
2663 							element.pattern = patternToRegExp(elementName);
2664 							patternElements.push(element);
2665 						} else
2666 							elements[elementName] = element;
2667 					}
2668 				}
2669 			}
2670 		};
2671 
2672 		function setValidElements(valid_elements) {
2673 			elements = {};
2674 			patternElements = [];
2675 
2676 			addValidElements(valid_elements);
2677 
2678 			each(schemaItems, function(element, name) {
2679 				children[name] = element.children;
2680 			});
2681 		};
2682 
2683 		// Adds custom non HTML elements to the schema
2684 		function addCustomElements(custom_elements) {
2685 			var customElementRegExp = /^(~)?(.+)$/;
2686 
2687 			if (custom_elements) {
2688 				each(split(custom_elements), function(rule) {
2689 					var matches = customElementRegExp.exec(rule),
2690 						inline = matches[1] === '~',
2691 						cloneName = inline ? 'span' : 'div',
2692 						name = matches[2];
2693 
2694 					children[name] = children[cloneName];
2695 					customElementsMap[name] = cloneName;
2696 
2697 					// If it's not marked as inline then add it to valid block elements
2698 					if (!inline)
2699 						blockElementsMap[name] = {};
2700 
2701 					// Add custom elements at span/div positions
2702 					each(children, function(element, child) {
2703 						if (element[cloneName])
2704 							element[name] = element[cloneName];
2705 					});
2706 				});
2707 			}
2708 		};
2709 
2710 		// Adds valid children to the schema object
2711 		function addValidChildren(valid_children) {
2712 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2713 
2714 			if (valid_children) {
2715 				each(split(valid_children), function(rule) {
2716 					var matches = childRuleRegExp.exec(rule), parent, prefix;
2717 
2718 					if (matches) {
2719 						prefix = matches[1];
2720 
2721 						// Add/remove items from default
2722 						if (prefix)
2723 							parent = children[matches[2]];
2724 						else
2725 							parent = children[matches[2]] = {'#comment' : {}};
2726 
2727 						parent = children[matches[2]];
2728 
2729 						each(split(matches[3], '|'), function(child) {
2730 							if (prefix === '-')
2731 								delete parent[child];
2732 							else
2733 								parent[child] = {};
2734 						});
2735 					}
2736 				});
2737 			}
2738 		};
2739 
2740 		function getElementRule(name) {
2741 			var element = elements[name], i;
2742 
2743 			// Exact match found
2744 			if (element)
2745 				return element;
2746 
2747 			// No exact match then try the patterns
2748 			i = patternElements.length;
2749 			while (i--) {
2750 				element = patternElements[i];
2751 
2752 				if (element.pattern.test(name))
2753 					return element;
2754 			}
2755 		};
2756 
2757 		if (!settings.valid_elements) {
2758 			// No valid elements defined then clone the elements from the schema spec
2759 			each(schemaItems, function(element, name) {
2760 				elements[name] = {
2761 					attributes : element.attributes,
2762 					attributesOrder : element.attributesOrder
2763 				};
2764 
2765 				children[name] = element.children;
2766 			});
2767 
2768 			// Switch these on HTML4
2769 			if (settings.schema != "html5") {
2770 				each(split('strong/b,em/i'), function(item) {
2771 					item = split(item, '/');
2772 					elements[item[1]].outputName = item[0];
2773 				});
2774 			}
2775 
2776 			// Add default alt attribute for images
2777 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
2778 
2779 			// Remove these if they are empty by default
2780 			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
2781 				if (elements[name]) {
2782 					elements[name].removeEmpty = true;
2783 				}
2784 			});
2785 
2786 			// Padd these by default
2787 			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
2788 				elements[name].paddEmpty = true;
2789 			});
2790 		} else
2791 			setValidElements(settings.valid_elements);
2792 
2793 		addCustomElements(settings.custom_elements);
2794 		addValidChildren(settings.valid_children);
2795 		addValidElements(settings.extended_valid_elements);
2796 
2797 		// Todo: Remove this when we fix list handling to be valid
2798 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
2799 
2800 		// Delete invalid elements
2801 		if (settings.invalid_elements) {
2802 			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
2803 				if (elements[item])
2804 					delete elements[item];
2805 			});
2806 		}
2807 
2808 		// If the user didn't allow span only allow internal spans
2809 		if (!getElementRule('span'))
2810 			addValidElements('span[!data-mce-type|*]');
2811 
2812 		self.children = children;
2813 
2814 		self.styles = validStyles;
2815 
2816 		self.getBoolAttrs = function() {
2817 			return boolAttrMap;
2818 		};
2819 
2820 		self.getBlockElements = function() {
2821 			return blockElementsMap;
2822 		};
2823 
2824 		self.getShortEndedElements = function() {
2825 			return shortEndedElementsMap;
2826 		};
2827 
2828 		self.getSelfClosingElements = function() {
2829 			return selfClosingElementsMap;
2830 		};
2831 
2832 		self.getNonEmptyElements = function() {
2833 			return nonEmptyElementsMap;
2834 		};
2835 
2836 		self.getWhiteSpaceElements = function() {
2837 			return whiteSpaceElementsMap;
2838 		};
2839 
2840 		self.isValidChild = function(name, child) {
2841 			var parent = children[name];
2842 
2843 			return !!(parent && parent[child]);
2844 		};
2845 
2846 		self.isValid = function(name, attr) {
2847 			var attrPatterns, i, rule = getElementRule(name);
2848 
2849 			// Check if it's a valid element
2850 			if (rule) {
2851 				if (attr) {
2852 					// Check if attribute name exists
2853 					if (rule.attributes[attr]) {
2854 						return true;
2855 					}
2856 
2857 					// Check if attribute matches a regexp pattern
2858 					attrPatterns = rule.attributePatterns;
2859 					if (attrPatterns) {
2860 						i = attrPatterns.length;
2861 						while (i--) {
2862 							if (attrPatterns[i].pattern.test(name)) {
2863 								return true;
2864 							}
2865 						}
2866 					}
2867 				} else {
2868 					return true;
2869 				}
2870 			}
2871 
2872 			// No match
2873 			return false;
2874 		};
2875 		
2876 		self.getElementRule = getElementRule;
2877 
2878 		self.getCustomElements = function() {
2879 			return customElementsMap;
2880 		};
2881 
2882 		self.addValidElements = addValidElements;
2883 
2884 		self.setValidElements = setValidElements;
2885 
2886 		self.addCustomElements = addCustomElements;
2887 
2888 		self.addValidChildren = addValidChildren;
2889 	};
2890 })(tinymce);
2891 
2892 (function(tinymce) {
2893 	tinymce.html.SaxParser = function(settings, schema) {
2894 		var self = this, noop = function() {};
2895 
2896 		settings = settings || {};
2897 		self.schema = schema = schema || new tinymce.html.Schema();
2898 
2899 		if (settings.fix_self_closing !== false)
2900 			settings.fix_self_closing = true;
2901 
2902 		// Add handler functions from settings and setup default handlers
2903 		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2904 			if (name)
2905 				self[name] = settings[name] || noop;
2906 		});
2907 
2908 		self.parse = function(html) {
2909 			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2910 				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
2911 				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2912 				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
2913 
2914 			function processEndTag(name) {
2915 				var pos, i;
2916 
2917 				// Find position of parent of the same type
2918 				pos = stack.length;
2919 				while (pos--) {
2920 					if (stack[pos].name === name)
2921 						break;						
2922 				}
2923 
2924 				// Found parent
2925 				if (pos >= 0) {
2926 					// Close all the open elements
2927 					for (i = stack.length - 1; i >= pos; i--) {
2928 						name = stack[i];
2929 
2930 						if (name.valid)
2931 							self.end(name.name);
2932 					}
2933 
2934 					// Remove the open elements from the stack
2935 					stack.length = pos;
2936 				}
2937 			};
2938 
2939 			function parseAttribute(match, name, value, val2, val3) {
2940 				var attrRule, i;
2941 
2942 				name = name.toLowerCase();
2943 				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2944 
2945 				// Validate name and value
2946 				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2947 					attrRule = validAttributesMap[name];
2948 
2949 					// Find rule by pattern matching
2950 					if (!attrRule && validAttributePatterns) {
2951 						i = validAttributePatterns.length;
2952 						while (i--) {
2953 							attrRule = validAttributePatterns[i];
2954 							if (attrRule.pattern.test(name))
2955 								break;
2956 						}
2957 
2958 						// No rule matched
2959 						if (i === -1)
2960 							attrRule = null;
2961 					}
2962 
2963 					// No attribute rule found
2964 					if (!attrRule)
2965 						return;
2966 
2967 					// Validate value
2968 					if (attrRule.validValues && !(value in attrRule.validValues))
2969 						return;
2970 				}
2971 
2972 				// Add attribute to list and map
2973 				attrList.map[name] = value;
2974 				attrList.push({
2975 					name: name,
2976 					value: value
2977 				});
2978 			};
2979 
2980 			// Precompile RegExps and map objects
2981 			tokenRegExp = new RegExp('<(?:' +
2982 				'(?:!--([\\w\\W]*?)-->)|' + // Comment
2983 				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2984 				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2985 				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2986 				'(?:\\/([^>]+)>)|' + // End element
2987 				'(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
2988 			')', 'g');
2989 
2990 			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2991 			specialElements = {
2992 				'script' : /<\/script[^>]*>/gi,
2993 				'style' : /<\/style[^>]*>/gi,
2994 				'noscript' : /<\/noscript[^>]*>/gi
2995 			};
2996 
2997 			// Setup lookup tables for empty elements and boolean attributes
2998 			shortEndedElements = schema.getShortEndedElements();
2999 			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
3000 			fillAttrsMap = schema.getBoolAttrs();
3001 			validate = settings.validate;
3002 			removeInternalElements = settings.remove_internals;
3003 			fixSelfClosing = settings.fix_self_closing;
3004 			isIE = tinymce.isIE;
3005 			invalidPrefixRegExp = /^:/;
3006 
3007 			while (matches = tokenRegExp.exec(html)) {
3008 				// Text
3009 				if (index < matches.index)
3010 					self.text(decode(html.substr(index, matches.index - index)));
3011 
3012 				if (value = matches[6]) { // End element
3013 					value = value.toLowerCase();
3014 
3015 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3016 					if (isIE && invalidPrefixRegExp.test(value))
3017 						value = value.substr(1);
3018 
3019 					processEndTag(value);
3020 				} else if (value = matches[7]) { // Start element
3021 					value = value.toLowerCase();
3022 
3023 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3024 					if (isIE && invalidPrefixRegExp.test(value))
3025 						value = value.substr(1);
3026 
3027 					isShortEnded = value in shortEndedElements;
3028 
3029 					// Is self closing tag for example an <li> after an open <li>
3030 					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3031 						processEndTag(value);
3032 
3033 					// Validate element
3034 					if (!validate || (elementRule = schema.getElementRule(value))) {
3035 						isValidElement = true;
3036 
3037 						// Grab attributes map and patters when validation is enabled
3038 						if (validate) {
3039 							validAttributesMap = elementRule.attributes;
3040 							validAttributePatterns = elementRule.attributePatterns;
3041 						}
3042 
3043 						// Parse attributes
3044 						if (attribsValue = matches[8]) {
3045 							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3046 
3047 							// If the element has internal attributes then remove it if we are told to do so
3048 							if (isInternalElement && removeInternalElements)
3049 								isValidElement = false;
3050 
3051 							attrList = [];
3052 							attrList.map = {};
3053 
3054 							attribsValue.replace(attrRegExp, parseAttribute);
3055 						} else {
3056 							attrList = [];
3057 							attrList.map = {};
3058 						}
3059 
3060 						// Process attributes if validation is enabled
3061 						if (validate && !isInternalElement) {
3062 							attributesRequired = elementRule.attributesRequired;
3063 							attributesDefault = elementRule.attributesDefault;
3064 							attributesForced = elementRule.attributesForced;
3065 
3066 							// Handle forced attributes
3067 							if (attributesForced) {
3068 								i = attributesForced.length;
3069 								while (i--) {
3070 									attr = attributesForced[i];
3071 									name = attr.name;
3072 									attrValue = attr.value;
3073 
3074 									if (attrValue === '{$uid}')
3075 										attrValue = 'mce_' + idCount++;
3076 
3077 									attrList.map[name] = attrValue;
3078 									attrList.push({name: name, value: attrValue});
3079 								}
3080 							}
3081 
3082 							// Handle default attributes
3083 							if (attributesDefault) {
3084 								i = attributesDefault.length;
3085 								while (i--) {
3086 									attr = attributesDefault[i];
3087 									name = attr.name;
3088 
3089 									if (!(name in attrList.map)) {
3090 										attrValue = attr.value;
3091 
3092 										if (attrValue === '{$uid}')
3093 											attrValue = 'mce_' + idCount++;
3094 
3095 										attrList.map[name] = attrValue;
3096 										attrList.push({name: name, value: attrValue});
3097 									}
3098 								}
3099 							}
3100 
3101 							// Handle required attributes
3102 							if (attributesRequired) {
3103 								i = attributesRequired.length;
3104 								while (i--) {
3105 									if (attributesRequired[i] in attrList.map)
3106 										break;
3107 								}
3108 
3109 								// None of the required attributes where found
3110 								if (i === -1)
3111 									isValidElement = false;
3112 							}
3113 
3114 							// Invalidate element if it's marked as bogus
3115 							if (attrList.map['data-mce-bogus'])
3116 								isValidElement = false;
3117 						}
3118 
3119 						if (isValidElement)
3120 							self.start(value, attrList, isShortEnded);
3121 					} else
3122 						isValidElement = false;
3123 
3124 					// Treat script, noscript and style a bit different since they may include code that looks like elements
3125 					if (endRegExp = specialElements[value]) {
3126 						endRegExp.lastIndex = index = matches.index + matches[0].length;
3127 
3128 						if (matches = endRegExp.exec(html)) {
3129 							if (isValidElement)
3130 								text = html.substr(index, matches.index - index);
3131 
3132 							index = matches.index + matches[0].length;
3133 						} else {
3134 							text = html.substr(index);
3135 							index = html.length;
3136 						}
3137 
3138 						if (isValidElement && text.length > 0)
3139 							self.text(text, true);
3140 
3141 						if (isValidElement)
3142 							self.end(value);
3143 
3144 						tokenRegExp.lastIndex = index;
3145 						continue;
3146 					}
3147 
3148 					// Push value on to stack
3149 					if (!isShortEnded) {
3150 						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3151 							stack.push({name: value, valid: isValidElement});
3152 						else if (isValidElement)
3153 							self.end(value);
3154 					}
3155 				} else if (value = matches[1]) { // Comment
3156 					self.comment(value);
3157 				} else if (value = matches[2]) { // CDATA
3158 					self.cdata(value);
3159 				} else if (value = matches[3]) { // DOCTYPE
3160 					self.doctype(value);
3161 				} else if (value = matches[4]) { // PI
3162 					self.pi(value, matches[5]);
3163 				}
3164 
3165 				index = matches.index + matches[0].length;
3166 			}
3167 
3168 			// Text
3169 			if (index < html.length)
3170 				self.text(decode(html.substr(index)));
3171 
3172 			// Close any open elements
3173 			for (i = stack.length - 1; i >= 0; i--) {
3174 				value = stack[i];
3175 
3176 				if (value.valid)
3177 					self.end(value.name);
3178 			}
3179 		};
3180 	}
3181 })(tinymce);
3182 
3183 (function(tinymce) {
3184 	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3185 		'#text' : 3,
3186 		'#comment' : 8,
3187 		'#cdata' : 4,
3188 		'#pi' : 7,
3189 		'#doctype' : 10,
3190 		'#document-fragment' : 11
3191 	};
3192 
3193 	// Walks the tree left/right
3194 	function walk(node, root_node, prev) {
3195 		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3196 
3197 		// Walk into nodes if it has a start
3198 		if (node[startName])
3199 			return node[startName];
3200 
3201 		// Return the sibling if it has one
3202 		if (node !== root_node) {
3203 			sibling = node[siblingName];
3204 
3205 			if (sibling)
3206 				return sibling;
3207 
3208 			// Walk up the parents to look for siblings
3209 			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3210 				sibling = parent[siblingName];
3211 
3212 				if (sibling)
3213 					return sibling;
3214 			}
3215 		}
3216 	};
3217 
3218 	function Node(name, type) {
3219 		this.name = name;
3220 		this.type = type;
3221 
3222 		if (type === 1) {
3223 			this.attributes = [];
3224 			this.attributes.map = {};
3225 		}
3226 	}
3227 
3228 	tinymce.extend(Node.prototype, {
3229 		replace : function(node) {
3230 			var self = this;
3231 
3232 			if (node.parent)
3233 				node.remove();
3234 
3235 			self.insert(node, self);
3236 			self.remove();
3237 
3238 			return self;
3239 		},
3240 
3241 		attr : function(name, value) {
3242 			var self = this, attrs, i, undef;
3243 
3244 			if (typeof name !== "string") {
3245 				for (i in name)
3246 					self.attr(i, name[i]);
3247 
3248 				return self;
3249 			}
3250 
3251 			if (attrs = self.attributes) {
3252 				if (value !== undef) {
3253 					// Remove attribute
3254 					if (value === null) {
3255 						if (name in attrs.map) {
3256 							delete attrs.map[name];
3257 
3258 							i = attrs.length;
3259 							while (i--) {
3260 								if (attrs[i].name === name) {
3261 									attrs = attrs.splice(i, 1);
3262 									return self;
3263 								}
3264 							}
3265 						}
3266 
3267 						return self;
3268 					}
3269 
3270 					// Set attribute
3271 					if (name in attrs.map) {
3272 						// Set attribute
3273 						i = attrs.length;
3274 						while (i--) {
3275 							if (attrs[i].name === name) {
3276 								attrs[i].value = value;
3277 								break;
3278 							}
3279 						}
3280 					} else
3281 						attrs.push({name: name, value: value});
3282 
3283 					attrs.map[name] = value;
3284 
3285 					return self;
3286 				} else {
3287 					return attrs.map[name];
3288 				}
3289 			}
3290 		},
3291 
3292 		clone : function() {
3293 			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3294 
3295 			// Clone element attributes
3296 			if (selfAttrs = self.attributes) {
3297 				cloneAttrs = [];
3298 				cloneAttrs.map = {};
3299 
3300 				for (i = 0, l = selfAttrs.length; i < l; i++) {
3301 					selfAttr = selfAttrs[i];
3302 
3303 					// Clone everything except id
3304 					if (selfAttr.name !== 'id') {
3305 						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3306 						cloneAttrs.map[selfAttr.name] = selfAttr.value;
3307 					}
3308 				}
3309 
3310 				clone.attributes = cloneAttrs;
3311 			}
3312 
3313 			clone.value = self.value;
3314 			clone.shortEnded = self.shortEnded;
3315 
3316 			return clone;
3317 		},
3318 
3319 		wrap : function(wrapper) {
3320 			var self = this;
3321 
3322 			self.parent.insert(wrapper, self);
3323 			wrapper.append(self);
3324 
3325 			return self;
3326 		},
3327 
3328 		unwrap : function() {
3329 			var self = this, node, next;
3330 
3331 			for (node = self.firstChild; node; ) {
3332 				next = node.next;
3333 				self.insert(node, self, true);
3334 				node = next;
3335 			}
3336 
3337 			self.remove();
3338 		},
3339 
3340 		remove : function() {
3341 			var self = this, parent = self.parent, next = self.next, prev = self.prev;
3342 
3343 			if (parent) {
3344 				if (parent.firstChild === self) {
3345 					parent.firstChild = next;
3346 
3347 					if (next)
3348 						next.prev = null;
3349 				} else {
3350 					prev.next = next;
3351 				}
3352 
3353 				if (parent.lastChild === self) {
3354 					parent.lastChild = prev;
3355 
3356 					if (prev)
3357 						prev.next = null;
3358 				} else {
3359 					next.prev = prev;
3360 				}
3361 
3362 				self.parent = self.next = self.prev = null;
3363 			}
3364 
3365 			return self;
3366 		},
3367 
3368 		append : function(node) {
3369 			var self = this, last;
3370 
3371 			if (node.parent)
3372 				node.remove();
3373 
3374 			last = self.lastChild;
3375 			if (last) {
3376 				last.next = node;
3377 				node.prev = last;
3378 				self.lastChild = node;
3379 			} else
3380 				self.lastChild = self.firstChild = node;
3381 
3382 			node.parent = self;
3383 
3384 			return node;
3385 		},
3386 
3387 		insert : function(node, ref_node, before) {
3388 			var parent;
3389 
3390 			if (node.parent)
3391 				node.remove();
3392 
3393 			parent = ref_node.parent || this;
3394 
3395 			if (before) {
3396 				if (ref_node === parent.firstChild)
3397 					parent.firstChild = node;
3398 				else
3399 					ref_node.prev.next = node;
3400 
3401 				node.prev = ref_node.prev;
3402 				node.next = ref_node;
3403 				ref_node.prev = node;
3404 			} else {
3405 				if (ref_node === parent.lastChild)
3406 					parent.lastChild = node;
3407 				else
3408 					ref_node.next.prev = node;
3409 
3410 				node.next = ref_node.next;
3411 				node.prev = ref_node;
3412 				ref_node.next = node;
3413 			}
3414 
3415 			node.parent = parent;
3416 
3417 			return node;
3418 		},
3419 
3420 		getAll : function(name) {
3421 			var self = this, node, collection = [];
3422 
3423 			for (node = self.firstChild; node; node = walk(node, self)) {
3424 				if (node.name === name)
3425 					collection.push(node);
3426 			}
3427 
3428 			return collection;
3429 		},
3430 
3431 		empty : function() {
3432 			var self = this, nodes, i, node;
3433 
3434 			// Remove all children
3435 			if (self.firstChild) {
3436 				nodes = [];
3437 
3438 				// Collect the children
3439 				for (node = self.firstChild; node; node = walk(node, self))
3440 					nodes.push(node);
3441 
3442 				// Remove the children
3443 				i = nodes.length;
3444 				while (i--) {
3445 					node = nodes[i];
3446 					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3447 				}
3448 			}
3449 
3450 			self.firstChild = self.lastChild = null;
3451 
3452 			return self;
3453 		},
3454 
3455 		isEmpty : function(elements) {
3456 			var self = this, node = self.firstChild, i, name;
3457 
3458 			if (node) {
3459 				do {
3460 					if (node.type === 1) {
3461 						// Ignore bogus elements
3462 						if (node.attributes.map['data-mce-bogus'])
3463 							continue;
3464 
3465 						// Keep empty elements like <img />
3466 						if (elements[node.name])
3467 							return false;
3468 
3469 						// Keep elements with data attributes or name attribute like <a name="1"></a>
3470 						i = node.attributes.length;
3471 						while (i--) {
3472 							name = node.attributes[i].name;
3473 							if (name === "name" || name.indexOf('data-') === 0)
3474 								return false;
3475 						}
3476 					}
3477 
3478 					// Keep comments
3479 					if (node.type === 8)
3480 						return false;
3481 					
3482 					// Keep non whitespace text nodes
3483 					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3484 						return false;
3485 				} while (node = walk(node, self));
3486 			}
3487 
3488 			return true;
3489 		},
3490 
3491 		walk : function(prev) {
3492 			return walk(this, null, prev);
3493 		}
3494 	});
3495 
3496 	tinymce.extend(Node, {
3497 		create : function(name, attrs) {
3498 			var node, attrName;
3499 
3500 			// Create node
3501 			node = new Node(name, typeLookup[name] || 1);
3502 
3503 			// Add attributes if needed
3504 			if (attrs) {
3505 				for (attrName in attrs)
3506 					node.attr(attrName, attrs[attrName]);
3507 			}
3508 
3509 			return node;
3510 		}
3511 	});
3512 
3513 	tinymce.html.Node = Node;
3514 })(tinymce);
3515 
3516 (function(tinymce) {
3517 	var Node = tinymce.html.Node;
3518 
3519 	tinymce.html.DomParser = function(settings, schema) {
3520 		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3521 
3522 		settings = settings || {};
3523 		settings.validate = "validate" in settings ? settings.validate : true;
3524 		settings.root_name = settings.root_name || 'body';
3525 		self.schema = schema = schema || new tinymce.html.Schema();
3526 
3527 		function fixInvalidChildren(nodes) {
3528 			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3529 				childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3530 
3531 			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3532 			nonEmptyElements = schema.getNonEmptyElements();
3533 
3534 			for (ni = 0; ni < nodes.length; ni++) {
3535 				node = nodes[ni];
3536 
3537 				// Already removed
3538 				if (!node.parent)
3539 					continue;
3540 
3541 				// Get list of all parent nodes until we find a valid parent to stick the child into
3542 				parents = [node];
3543 				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3544 					parents.push(parent);
3545 
3546 				// Found a suitable parent
3547 				if (parent && parents.length > 1) {
3548 					// Reverse the array since it makes looping easier
3549 					parents.reverse();
3550 
3551 					// Clone the related parent and insert that after the moved node
3552 					newParent = currentNode = self.filterNode(parents[0].clone());
3553 
3554 					// Start cloning and moving children on the left side of the target node
3555 					for (i = 0; i < parents.length - 1; i++) {
3556 						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3557 							tempNode = self.filterNode(parents[i].clone());
3558 							currentNode.append(tempNode);
3559 						} else
3560 							tempNode = currentNode;
3561 
3562 						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3563 							nextNode = childNode.next;
3564 							tempNode.append(childNode);
3565 							childNode = nextNode;
3566 						}
3567 
3568 						currentNode = tempNode;
3569 					}
3570 
3571 					if (!newParent.isEmpty(nonEmptyElements)) {
3572 						parent.insert(newParent, parents[0], true);
3573 						parent.insert(node, newParent);
3574 					} else {
3575 						parent.insert(node, parents[0], true);
3576 					}
3577 
3578 					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3579 					parent = parents[0];
3580 					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3581 						parent.empty().remove();
3582 					}
3583 				} else if (node.parent) {
3584 					// If it's an LI try to find a UL/OL for it or wrap it
3585 					if (node.name === 'li') {
3586 						sibling = node.prev;
3587 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3588 							sibling.append(node);
3589 							continue;
3590 						}
3591 
3592 						sibling = node.next;
3593 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3594 							sibling.insert(node, sibling.firstChild, true);
3595 							continue;
3596 						}
3597 
3598 						node.wrap(self.filterNode(new Node('ul', 1)));
3599 						continue;
3600 					}
3601 
3602 					// Try wrapping the element in a DIV
3603 					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3604 						node.wrap(self.filterNode(new Node('div', 1)));
3605 					} else {
3606 						// We failed wrapping it, then remove or unwrap it
3607 						if (node.name === 'style' || node.name === 'script')
3608 							node.empty().remove();
3609 						else
3610 							node.unwrap();
3611 					}
3612 				}
3613 			}
3614 		};
3615 
3616 		self.filterNode = function(node) {
3617 			var i, name, list;
3618 
3619 			// Run element filters
3620 			if (name in nodeFilters) {
3621 				list = matchedNodes[name];
3622 
3623 				if (list)
3624 					list.push(node);
3625 				else
3626 					matchedNodes[name] = [node];
3627 			}
3628 
3629 			// Run attribute filters
3630 			i = attributeFilters.length;
3631 			while (i--) {
3632 				name = attributeFilters[i].name;
3633 
3634 				if (name in node.attributes.map) {
3635 					list = matchedAttributes[name];
3636 
3637 					if (list)
3638 						list.push(node);
3639 					else
3640 						matchedAttributes[name] = [node];
3641 				}
3642 			}
3643 
3644 			return node;
3645 		};
3646 
3647 		self.addNodeFilter = function(name, callback) {
3648 			tinymce.each(tinymce.explode(name), function(name) {
3649 				var list = nodeFilters[name];
3650 
3651 				if (!list)
3652 					nodeFilters[name] = list = [];
3653 
3654 				list.push(callback);
3655 			});
3656 		};
3657 
3658 		self.addAttributeFilter = function(name, callback) {
3659 			tinymce.each(tinymce.explode(name), function(name) {
3660 				var i;
3661 
3662 				for (i = 0; i < attributeFilters.length; i++) {
3663 					if (attributeFilters[i].name === name) {
3664 						attributeFilters[i].callbacks.push(callback);
3665 						return;
3666 					}
3667 				}
3668 
3669 				attributeFilters.push({name: name, callbacks: [callback]});
3670 			});
3671 		};
3672 
3673 		self.parse = function(html, args) {
3674 			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3675 				blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3676 				endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3677 
3678 			args = args || {};
3679 			matchedNodes = {};
3680 			matchedAttributes = {};
3681 			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3682 			nonEmptyElements = schema.getNonEmptyElements();
3683 			children = schema.children;
3684 			validate = settings.validate;
3685 			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3686 
3687 			whiteSpaceElements = schema.getWhiteSpaceElements();
3688 			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3689 			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3690 			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3691 			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3692 
3693 			function addRootBlocks() {
3694 				var node = rootNode.firstChild, next, rootBlockNode;
3695 
3696 				while (node) {
3697 					next = node.next;
3698 
3699 					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3700 						if (!rootBlockNode) {
3701 							// Create a new root block element
3702 							rootBlockNode = createNode(rootBlockName, 1);
3703 							rootNode.insert(rootBlockNode, node);
3704 							rootBlockNode.append(node);
3705 						} else
3706 							rootBlockNode.append(node);
3707 					} else {
3708 						rootBlockNode = null;
3709 					}
3710 
3711 					node = next;
3712 				};
3713 			};
3714 
3715 			function createNode(name, type) {
3716 				var node = new Node(name, type), list;
3717 
3718 				if (name in nodeFilters) {
3719 					list = matchedNodes[name];
3720 
3721 					if (list)
3722 						list.push(node);
3723 					else
3724 						matchedNodes[name] = [node];
3725 				}
3726 
3727 				return node;
3728 			};
3729 
3730 			function removeWhitespaceBefore(node) {
3731 				var textNode, textVal, sibling;
3732 
3733 				for (textNode = node.prev; textNode && textNode.type === 3; ) {
3734 					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3735 
3736 					if (textVal.length > 0) {
3737 						textNode.value = textVal;
3738 						textNode = textNode.prev;
3739 					} else {
3740 						sibling = textNode.prev;
3741 						textNode.remove();
3742 						textNode = sibling;
3743 					}
3744 				}
3745 			};
3746 
3747 			function cloneAndExcludeBlocks(input) {
3748 				var name, output = {};
3749 
3750 				for (name in input) {
3751 					if (name !== 'li' && name != 'p') {
3752 						output[name] = input[name];
3753 					}
3754 				}
3755 
3756 				return output;
3757 			};
3758 
3759 			parser = new tinymce.html.SaxParser({
3760 				validate : validate,
3761 
3762 				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
3763 				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
3764 
3765 				cdata: function(text) {
3766 					node.append(createNode('#cdata', 4)).value = text;
3767 				},
3768 
3769 				text: function(text, raw) {
3770 					var textNode;
3771 
3772 					// Trim all redundant whitespace on non white space elements
3773 					if (!isInWhiteSpacePreservedElement) {
3774 						text = text.replace(allWhiteSpaceRegExp, ' ');
3775 
3776 						if (node.lastChild && blockElements[node.lastChild.name])
3777 							text = text.replace(startWhiteSpaceRegExp, '');
3778 					}
3779 
3780 					// Do we need to create the node
3781 					if (text.length !== 0) {
3782 						textNode = createNode('#text', 3);
3783 						textNode.raw = !!raw;
3784 						node.append(textNode).value = text;
3785 					}
3786 				},
3787 
3788 				comment: function(text) {
3789 					node.append(createNode('#comment', 8)).value = text;
3790 				},
3791 
3792 				pi: function(name, text) {
3793 					node.append(createNode(name, 7)).value = text;
3794 					removeWhitespaceBefore(node);
3795 				},
3796 
3797 				doctype: function(text) {
3798 					var newNode;
3799 		
3800 					newNode = node.append(createNode('#doctype', 10));
3801 					newNode.value = text;
3802 					removeWhitespaceBefore(node);
3803 				},
3804 
3805 				start: function(name, attrs, empty) {
3806 					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
3807 
3808 					elementRule = validate ? schema.getElementRule(name) : {};
3809 					if (elementRule) {
3810 						newNode = createNode(elementRule.outputName || name, 1);
3811 						newNode.attributes = attrs;
3812 						newNode.shortEnded = empty;
3813 
3814 						node.append(newNode);
3815 
3816 						// Check if node is valid child of the parent node is the child is
3817 						// unknown we don't collect it since it's probably a custom element
3818 						parent = children[node.name];
3819 						if (parent && children[newNode.name] && !parent[newNode.name])
3820 							invalidChildren.push(newNode);
3821 
3822 						attrFiltersLen = attributeFilters.length;
3823 						while (attrFiltersLen--) {
3824 							attrName = attributeFilters[attrFiltersLen].name;
3825 
3826 							if (attrName in attrs.map) {
3827 								list = matchedAttributes[attrName];
3828 
3829 								if (list)
3830 									list.push(newNode);
3831 								else
3832 									matchedAttributes[attrName] = [newNode];
3833 							}
3834 						}
3835 
3836 						// Trim whitespace before block
3837 						if (blockElements[name])
3838 							removeWhitespaceBefore(newNode);
3839 
3840 						// Change current node if the element wasn't empty i.e not <br /> or <img />
3841 						if (!empty)
3842 							node = newNode;
3843 
3844 						// Check if we are inside a whitespace preserved element
3845 						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3846 							isInWhiteSpacePreservedElement = true;
3847 						}
3848 					}
3849 				},
3850 
3851 				end: function(name) {
3852 					var textNode, elementRule, text, sibling, tempNode;
3853 
3854 					elementRule = validate ? schema.getElementRule(name) : {};
3855 					if (elementRule) {
3856 						if (blockElements[name]) {
3857 							if (!isInWhiteSpacePreservedElement) {
3858 								// Trim whitespace of the first node in a block
3859 								textNode = node.firstChild;
3860 								if (textNode && textNode.type === 3) {
3861 									text = textNode.value.replace(startWhiteSpaceRegExp, '');
3862 
3863 									// Any characters left after trim or should we remove it
3864 									if (text.length > 0) {
3865 										textNode.value = text;
3866 										textNode = textNode.next;
3867 									} else {
3868 										sibling = textNode.next;
3869 										textNode.remove();
3870 										textNode = sibling;
3871 									}
3872 
3873 									// Remove any pure whitespace siblings
3874 									while (textNode && textNode.type === 3) {
3875 										text = textNode.value;
3876 										sibling = textNode.next;
3877 
3878 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3879 											textNode.remove();
3880 											textNode = sibling;
3881 										}
3882 
3883 										textNode = sibling;
3884 									}
3885 								}
3886 
3887 								// Trim whitespace of the last node in a block
3888 								textNode = node.lastChild;
3889 								if (textNode && textNode.type === 3) {
3890 									text = textNode.value.replace(endWhiteSpaceRegExp, '');
3891 
3892 									// Any characters left after trim or should we remove it
3893 									if (text.length > 0) {
3894 										textNode.value = text;
3895 										textNode = textNode.prev;
3896 									} else {
3897 										sibling = textNode.prev;
3898 										textNode.remove();
3899 										textNode = sibling;
3900 									}
3901 
3902 									// Remove any pure whitespace siblings
3903 									while (textNode && textNode.type === 3) {
3904 										text = textNode.value;
3905 										sibling = textNode.prev;
3906 
3907 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3908 											textNode.remove();
3909 											textNode = sibling;
3910 										}
3911 
3912 										textNode = sibling;
3913 									}
3914 								}
3915 							}
3916 
3917 							// Trim start white space
3918 							textNode = node.prev;
3919 							if (textNode && textNode.type === 3) {
3920 								text = textNode.value.replace(startWhiteSpaceRegExp, '');
3921 
3922 								if (text.length > 0)
3923 									textNode.value = text;
3924 								else
3925 									textNode.remove();
3926 							}
3927 						}
3928 
3929 						// Check if we exited a whitespace preserved element
3930 						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3931 							isInWhiteSpacePreservedElement = false;
3932 						}
3933 
3934 						// Handle empty nodes
3935 						if (elementRule.removeEmpty || elementRule.paddEmpty) {
3936 							if (node.isEmpty(nonEmptyElements)) {
3937 								if (elementRule.paddEmpty)
3938 									node.empty().append(new Node('#text', '3')).value = '\u00a0';
3939 								else {
3940 									// Leave nodes that have a name like <a name="name">
3941 									if (!node.attributes.map.name && !node.attributes.map.id) {
3942 										tempNode = node.parent;
3943 										node.empty().remove();
3944 										node = tempNode;
3945 										return;
3946 									}
3947 								}
3948 							}
3949 						}
3950 
3951 						node = node.parent;
3952 					}
3953 				}
3954 			}, schema);
3955 
3956 			rootNode = node = new Node(args.context || settings.root_name, 11);
3957 
3958 			parser.parse(html);
3959 
3960 			// Fix invalid children or report invalid children in a contextual parsing
3961 			if (validate && invalidChildren.length) {
3962 				if (!args.context)
3963 					fixInvalidChildren(invalidChildren);
3964 				else
3965 					args.invalid = true;
3966 			}
3967 
3968 			// Wrap nodes in the root into block elements if the root is body
3969 			if (rootBlockName && rootNode.name == 'body')
3970 				addRootBlocks();
3971 
3972 			// Run filters only when the contents is valid
3973 			if (!args.invalid) {
3974 				// Run node filters
3975 				for (name in matchedNodes) {
3976 					list = nodeFilters[name];
3977 					nodes = matchedNodes[name];
3978 
3979 					// Remove already removed children
3980 					fi = nodes.length;
3981 					while (fi--) {
3982 						if (!nodes[fi].parent)
3983 							nodes.splice(fi, 1);
3984 					}
3985 
3986 					for (i = 0, l = list.length; i < l; i++)
3987 						list[i](nodes, name, args);
3988 				}
3989 
3990 				// Run attribute filters
3991 				for (i = 0, l = attributeFilters.length; i < l; i++) {
3992 					list = attributeFilters[i];
3993 
3994 					if (list.name in matchedAttributes) {
3995 						nodes = matchedAttributes[list.name];
3996 
3997 						// Remove already removed children
3998 						fi = nodes.length;
3999 						while (fi--) {
4000 							if (!nodes[fi].parent)
4001 								nodes.splice(fi, 1);
4002 						}
4003 
4004 						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
4005 							list.callbacks[fi](nodes, list.name, args);
4006 					}
4007 				}
4008 			}
4009 
4010 			return rootNode;
4011 		};
4012 
4013 		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4014 		// make it possible to place the caret inside empty blocks. This logic tries to remove
4015 		// these elements and keep br elements that where intended to be there intact
4016 		if (settings.remove_trailing_brs) {
4017 			self.addNodeFilter('br', function(nodes, name) {
4018 				var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4019 					nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4020 
4021 				// Remove brs from body element as well
4022 				blockElements.body = 1;
4023 
4024 				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4025 				for (i = 0; i < l; i++) {
4026 					node = nodes[i];
4027 					parent = node.parent;
4028 
4029 					if (blockElements[node.parent.name] && node === parent.lastChild) {
4030 						// Loop all nodes to the left of the current node and check for other BR elements
4031 						// excluding bookmarks since they are invisible
4032 						prev = node.prev;
4033 						while (prev) {
4034 							prevName = prev.name;
4035 
4036 							// Ignore bookmarks
4037 							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4038 								// Found a non BR element
4039 								if (prevName !== "br")
4040 									break;
4041 	
4042 								// Found another br it's a <br><br> structure then don't remove anything
4043 								if (prevName === 'br') {
4044 									node = null;
4045 									break;
4046 								}
4047 							}
4048 
4049 							prev = prev.prev;
4050 						}
4051 
4052 						if (node) {
4053 							node.remove();
4054 
4055 							// Is the parent to be considered empty after we removed the BR
4056 							if (parent.isEmpty(nonEmptyElements)) {
4057 								elementRule = schema.getElementRule(parent.name);
4058 
4059 								// Remove or padd the element depending on schema rule
4060 								if (elementRule) {
4061 									if (elementRule.removeEmpty)
4062 										parent.remove();
4063 									else if (elementRule.paddEmpty)
4064 										parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4065 								}
4066 							}
4067 						}
4068 					} else {
4069 						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 
4070 						lastParent = node;
4071 						while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4072 							lastParent = parent;
4073 
4074 							if (blockElements[parent.name]) {
4075 								break;
4076 							}
4077 
4078 							parent = parent.parent;
4079 						}
4080 
4081 						if (lastParent === parent) {
4082 							textNode = new tinymce.html.Node('#text', 3);
4083 							textNode.value = '\u00a0';
4084 							node.replace(textNode);
4085 						}
4086 					}
4087 				}
4088 			});
4089 		}
4090 
4091 		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4092 		if (!settings.allow_html_in_named_anchor) {
4093 			self.addAttributeFilter('id,name', function(nodes, name) {
4094 				var i = nodes.length, sibling, prevSibling, parent, node;
4095 
4096 				while (i--) {
4097 					node = nodes[i];
4098 					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4099 						parent = node.parent;
4100 
4101 						// Move children after current node
4102 						sibling = node.lastChild;
4103 						do {
4104 							prevSibling = sibling.prev;
4105 							parent.insert(sibling, node);
4106 							sibling = prevSibling;
4107 						} while (sibling);
4108 					}
4109 				}
4110 			});
4111 		}
4112 	}
4113 })(tinymce);
4114 
4115 tinymce.html.Writer = function(settings) {
4116 	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4117 
4118 	settings = settings || {};
4119 	indent = settings.indent;
4120 	indentBefore = tinymce.makeMap(settings.indent_before || '');
4121 	indentAfter = tinymce.makeMap(settings.indent_after || '');
4122 	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4123 	htmlOutput = settings.element_format == "html";
4124 
4125 	return {
4126 		start: function(name, attrs, empty) {
4127 			var i, l, attr, value;
4128 
4129 			if (indent && indentBefore[name] && html.length > 0) {
4130 				value = html[html.length - 1];
4131 
4132 				if (value.length > 0 && value !== '\n')
4133 					html.push('\n');
4134 			}
4135 
4136 			html.push('<', name);
4137 
4138 			if (attrs) {
4139 				for (i = 0, l = attrs.length; i < l; i++) {
4140 					attr = attrs[i];
4141 					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4142 				}
4143 			}
4144 
4145 			if (!empty || htmlOutput)
4146 				html[html.length] = '>';
4147 			else
4148 				html[html.length] = ' />';
4149 
4150 			if (empty && indent && indentAfter[name] && html.length > 0) {
4151 				value = html[html.length - 1];
4152 
4153 				if (value.length > 0 && value !== '\n')
4154 					html.push('\n');
4155 			}
4156 		},
4157 
4158 		end: function(name) {
4159 			var value;
4160 
4161 			/*if (indent && indentBefore[name] && html.length > 0) {
4162 				value = html[html.length - 1];
4163 
4164 				if (value.length > 0 && value !== '\n')
4165 					html.push('\n');
4166 			}*/
4167 
4168 			html.push('</', name, '>');
4169 
4170 			if (indent && indentAfter[name] && html.length > 0) {
4171 				value = html[html.length - 1];
4172 
4173 				if (value.length > 0 && value !== '\n')
4174 					html.push('\n');
4175 			}
4176 		},
4177 
4178 		text: function(text, raw) {
4179 			if (text.length > 0)
4180 				html[html.length] = raw ? text : encode(text);
4181 		},
4182 
4183 		cdata: function(text) {
4184 			html.push('<![CDATA[', text, ']]>');
4185 		},
4186 
4187 		comment: function(text) {
4188 			html.push('<!--', text, '-->');
4189 		},
4190 
4191 		pi: function(name, text) {
4192 			if (text)
4193 				html.push('<?', name, ' ', text, '?>');
4194 			else
4195 				html.push('<?', name, '?>');
4196 
4197 			if (indent)
4198 				html.push('\n');
4199 		},
4200 
4201 		doctype: function(text) {
4202 			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4203 		},
4204 
4205 		reset: function() {
4206 			html.length = 0;
4207 		},
4208 
4209 		getContent: function() {
4210 			return html.join('').replace(/\n$/, '');
4211 		}
4212 	};
4213 };
4214 
4215 (function(tinymce) {
4216 	tinymce.html.Serializer = function(settings, schema) {
4217 		var self = this, writer = new tinymce.html.Writer(settings);
4218 
4219 		settings = settings || {};
4220 		settings.validate = "validate" in settings ? settings.validate : true;
4221 
4222 		self.schema = schema = schema || new tinymce.html.Schema();
4223 		self.writer = writer;
4224 
4225 		self.serialize = function(node) {
4226 			var handlers, validate;
4227 
4228 			validate = settings.validate;
4229 
4230 			handlers = {
4231 				// #text
4232 				3: function(node, raw) {
4233 					writer.text(node.value, node.raw);
4234 				},
4235 
4236 				// #comment
4237 				8: function(node) {
4238 					writer.comment(node.value);
4239 				},
4240 
4241 				// Processing instruction
4242 				7: function(node) {
4243 					writer.pi(node.name, node.value);
4244 				},
4245 
4246 				// Doctype
4247 				10: function(node) {
4248 					writer.doctype(node.value);
4249 				},
4250 
4251 				// CDATA
4252 				4: function(node) {
4253 					writer.cdata(node.value);
4254 				},
4255 
4256 				// Document fragment
4257 				11: function(node) {
4258 					if ((node = node.firstChild)) {
4259 						do {
4260 							walk(node);
4261 						} while (node = node.next);
4262 					}
4263 				}
4264 			};
4265 
4266 			writer.reset();
4267 
4268 			function walk(node) {
4269 				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4270 
4271 				if (!handler) {
4272 					name = node.name;
4273 					isEmpty = node.shortEnded;
4274 					attrs = node.attributes;
4275 
4276 					// Sort attributes
4277 					if (validate && attrs && attrs.length > 1) {
4278 						sortedAttrs = [];
4279 						sortedAttrs.map = {};
4280 
4281 						elementRule = schema.getElementRule(node.name);
4282 						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4283 							attrName = elementRule.attributesOrder[i];
4284 
4285 							if (attrName in attrs.map) {
4286 								attrValue = attrs.map[attrName];
4287 								sortedAttrs.map[attrName] = attrValue;
4288 								sortedAttrs.push({name: attrName, value: attrValue});
4289 							}
4290 						}
4291 
4292 						for (i = 0, l = attrs.length; i < l; i++) {
4293 							attrName = attrs[i].name;
4294 
4295 							if (!(attrName in sortedAttrs.map)) {
4296 								attrValue = attrs.map[attrName];
4297 								sortedAttrs.map[attrName] = attrValue;
4298 								sortedAttrs.push({name: attrName, value: attrValue});
4299 							}
4300 						}
4301 
4302 						attrs = sortedAttrs;
4303 					}
4304 
4305 					writer.start(node.name, attrs, isEmpty);
4306 
4307 					if (!isEmpty) {
4308 						if ((node = node.firstChild)) {
4309 							do {
4310 								walk(node);
4311 							} while (node = node.next);
4312 						}
4313 
4314 						writer.end(name);
4315 					}
4316 				} else
4317 					handler(node);
4318 			}
4319 
4320 			// Serialize element and treat all non elements as fragments
4321 			if (node.type == 1 && !settings.inner)
4322 				walk(node);
4323 			else
4324 				handlers[11](node);
4325 
4326 			return writer.getContent();
4327 		};
4328 	}
4329 })(tinymce);
4330 
4331 // JSLint defined globals
4332 /*global tinymce:false, window:false */
4333 
4334 tinymce.dom = {};
4335 
4336 (function(namespace, expando) {
4337 	var w3cEventModel = !!document.addEventListener;
4338 
4339 	function addEvent(target, name, callback, capture) {
4340 		if (target.addEventListener) {
4341 			target.addEventListener(name, callback, capture || false);
4342 		} else if (target.attachEvent) {
4343 			target.attachEvent('on' + name, callback);
4344 		}
4345 	}
4346 
4347 	function removeEvent(target, name, callback, capture) {
4348 		if (target.removeEventListener) {
4349 			target.removeEventListener(name, callback, capture || false);
4350 		} else if (target.detachEvent) {
4351 			target.detachEvent('on' + name, callback);
4352 		}
4353 	}
4354 
4355 	function fix(original_event, data) {
4356 		var name, event = data || {};
4357 
4358 		// Dummy function that gets replaced on the delegation state functions
4359 		function returnFalse() {
4360 			return false;
4361 		}
4362 
4363 		// Dummy function that gets replaced on the delegation state functions
4364 		function returnTrue() {
4365 			return true;
4366 		}
4367 
4368 		// Copy all properties from the original event
4369 		for (name in original_event) {
4370 			// layerX/layerY is deprecated in Chrome and produces a warning
4371 			if (name !== "layerX" && name !== "layerY") {
4372 				event[name] = original_event[name];
4373 			}
4374 		}
4375 
4376 		// Normalize target IE uses srcElement
4377 		if (!event.target) {
4378 			event.target = event.srcElement || document;
4379 		}
4380 
4381 		// Add preventDefault method
4382 		event.preventDefault = function() {
4383 			event.isDefaultPrevented = returnTrue;
4384 
4385 			// Execute preventDefault on the original event object
4386 			if (original_event) {
4387 				if (original_event.preventDefault) {
4388 					original_event.preventDefault();
4389 				} else {
4390 					original_event.returnValue = false; // IE
4391 				}
4392 			}
4393 		};
4394 
4395 		// Add stopPropagation
4396 		event.stopPropagation = function() {
4397 			event.isPropagationStopped = returnTrue;
4398 
4399 			// Execute stopPropagation on the original event object
4400 			if (original_event) {
4401 				if (original_event.stopPropagation) {
4402 					original_event.stopPropagation();
4403 				} else {
4404 					original_event.cancelBubble = true; // IE
4405 				}
4406 			 }
4407 		};
4408 
4409 		// Add stopImmediatePropagation
4410 		event.stopImmediatePropagation = function() {
4411 			event.isImmediatePropagationStopped = returnTrue;
4412 			event.stopPropagation();
4413 		};
4414 
4415 		// Add event delegation states
4416 		if (!event.isDefaultPrevented) {
4417 			event.isDefaultPrevented = returnFalse;
4418 			event.isPropagationStopped = returnFalse;
4419 			event.isImmediatePropagationStopped = returnFalse;
4420 		}
4421 
4422 		return event;
4423 	}
4424 
4425 	function bindOnReady(win, callback, event_utils) {
4426 		var doc = win.document, event = {type: 'ready'};
4427 
4428 		// Gets called when the DOM is ready
4429 		function readyHandler() {
4430 			if (!event_utils.domLoaded) {
4431 				event_utils.domLoaded = true;
4432 				callback(event);
4433 			}
4434 		}
4435 
4436 		// Use W3C method
4437 		if (w3cEventModel) {
4438 			addEvent(win, 'DOMContentLoaded', readyHandler);
4439 		} else {
4440 			// Use IE method
4441 			addEvent(doc, "readystatechange", function() {
4442 				if (doc.readyState === "complete") {
4443 					removeEvent(doc, "readystatechange", arguments.callee);
4444 					readyHandler();
4445 				}
4446 			});
4447 
4448 			// Wait until we can scroll, when we can the DOM is initialized
4449 			if (doc.documentElement.doScroll && win === win.top) {
4450 				(function() {
4451 					try {
4452 						// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4453 						// http://javascript.nwbox.com/IEContentLoaded/
4454 						doc.documentElement.doScroll("left");
4455 					} catch (ex) {
4456 						setTimeout(arguments.callee, 0);
4457 						return;
4458 					}
4459 
4460 					readyHandler();
4461 				})();
4462 			}
4463 		}
4464 
4465 		// Fallback if any of the above methods should fail for some odd reason
4466 		addEvent(win, 'load', readyHandler);
4467 	}
4468 
4469 	function EventUtils(proxy) {
4470 		var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4471 
4472 		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4473 		hasFocusIn = "onfocusin" in document.documentElement;
4474 		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4475 		count = 1;
4476 
4477 		// State if the DOMContentLoaded was executed or not
4478 		self.domLoaded = false;
4479 		self.events = events;
4480 
4481 		function executeHandlers(evt, id) {
4482 			var callbackList, i, l, callback;
4483 
4484 			callbackList = events[id][evt.type];
4485 			if (callbackList) {
4486 				for (i = 0, l = callbackList.length; i < l; i++) {
4487 					callback = callbackList[i];
4488 					
4489 					// Check if callback exists might be removed if a unbind is called inside the callback
4490 					if (callback && callback.func.call(callback.scope, evt) === false) {
4491 						evt.preventDefault();
4492 					}
4493 
4494 					// Should we stop propagation to immediate listeners
4495 					if (evt.isImmediatePropagationStopped()) {
4496 						return;
4497 					}
4498 				}
4499 			}
4500 		}
4501 
4502 		self.bind = function(target, names, callback, scope) {
4503 			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4504 
4505 			// Native event handler function patches the event and executes the callbacks for the expando
4506 			function defaultNativeHandler(evt) {
4507 				executeHandlers(fix(evt || win.event), id);
4508 			}
4509 
4510 			// Don't bind to text nodes or comments
4511 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4512 				return;
4513 			}
4514 
4515 			// Create or get events id for the target
4516 			if (!target[expando]) {
4517 				id = count++;
4518 				target[expando] = id;
4519 				events[id] = {};
4520 			} else {
4521 				id = target[expando];
4522 
4523 				if (!events[id]) {
4524 					events[id] = {};
4525 				}
4526 			}
4527 
4528 			// Setup the specified scope or use the target as a default
4529 			scope = scope || target;
4530 
4531 			// Split names and bind each event, enables you to bind multiple events with one call
4532 			names = names.split(' ');
4533 			i = names.length;
4534 			while (i--) {
4535 				name = names[i];
4536 				nativeHandler = defaultNativeHandler;
4537 				fakeName = capture = false;
4538 
4539 				// Use ready instead of DOMContentLoaded
4540 				if (name === "DOMContentLoaded") {
4541 					name = "ready";
4542 				}
4543 
4544 				// DOM is already ready
4545 				if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4546 					self.domLoaded = true;
4547 					callback.call(scope, fix({type: name}));
4548 					continue;
4549 				}
4550 
4551 				// Handle mouseenter/mouseleaver
4552 				if (!hasMouseEnterLeave) {
4553 					fakeName = mouseEnterLeave[name];
4554 
4555 					if (fakeName) {
4556 						nativeHandler = function(evt) {
4557 							var current, related;
4558 
4559 							current = evt.currentTarget;
4560 							related = evt.relatedTarget;
4561 
4562 							// Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4563 							if (related && current.contains) {
4564 								// Use contains for performance
4565 								related = current.contains(related);
4566 							} else {
4567 								while (related && related !== current) {
4568 									related = related.parentNode;
4569 								}
4570 							}
4571 
4572 							// Fire fake event
4573 							if (!related) {
4574 								evt = fix(evt || win.event);
4575 								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4576 								evt.target = current;
4577 								executeHandlers(evt, id);
4578 							}
4579 						};
4580 					}
4581 				}
4582 
4583 				// Fake bubbeling of focusin/focusout
4584 				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4585 					capture = true;
4586 					fakeName = name === "focusin" ? "focus" : "blur";
4587 					nativeHandler = function(evt) {
4588 						evt = fix(evt || win.event);
4589 						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4590 						executeHandlers(evt, id);
4591 					};
4592 				}
4593 
4594 				// Setup callback list and bind native event
4595 				callbackList = events[id][name];
4596 				if (!callbackList) {
4597 					events[id][name] = callbackList = [{func: callback, scope: scope}];
4598 					callbackList.fakeName = fakeName;
4599 					callbackList.capture = capture;
4600 
4601 					// Add the nativeHandler to the callback list so that we can later unbind it
4602 					callbackList.nativeHandler = nativeHandler;
4603 					if (!w3cEventModel) {
4604 						callbackList.proxyHandler = proxy(id);
4605 					}
4606 
4607 					// Check if the target has native events support
4608 					if (name === "ready") {
4609 						bindOnReady(target, nativeHandler, self);
4610 					} else {
4611 						addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4612 					}
4613 				} else {
4614 					// If it already has an native handler then just push the callback
4615 					callbackList.push({func: callback, scope: scope});
4616 				}
4617 			}
4618 
4619 			target = callbackList = 0; // Clean memory for IE
4620 
4621 			return callback;
4622 		};
4623 
4624 		self.unbind = function(target, names, callback) {
4625 			var id, callbackList, i, ci, name, eventMap;
4626 
4627 			// Don't bind to text nodes or comments
4628 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4629 				return self;
4630 			}
4631 
4632 			// Unbind event or events if the target has the expando
4633 			id = target[expando];
4634 			if (id) {
4635 				eventMap = events[id];
4636 
4637 				// Specific callback
4638 				if (names) {
4639 					names = names.split(' ');
4640 					i = names.length;
4641 					while (i--) {
4642 						name = names[i];
4643 						callbackList = eventMap[name];
4644 
4645 						// Unbind the event if it exists in the map
4646 						if (callbackList) {
4647 							// Remove specified callback
4648 							if (callback) {
4649 								ci = callbackList.length;
4650 								while (ci--) {
4651 									if (callbackList[ci].func === callback) {
4652 										callbackList.splice(ci, 1);
4653 									}
4654 								}
4655 							}
4656 
4657 							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
4658 							if (!callback || callbackList.length === 0) {
4659 								delete eventMap[name];
4660 								removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4661 							}
4662 						}
4663 					}
4664 				} else {
4665 					// All events for a specific element
4666 					for (name in eventMap) {
4667 						callbackList = eventMap[name];
4668 						removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4669 					}
4670 
4671 					eventMap = {};
4672 				}
4673 
4674 				// Check if object is empty, if it isn't then we won't remove the expando map
4675 				for (name in eventMap) {
4676 					return self;
4677 				}
4678 
4679 				// Delete event object
4680 				delete events[id];
4681 
4682 				// Remove expando from target
4683 				try {
4684 					// IE will fail here since it can't delete properties from window
4685 					delete target[expando];
4686 				} catch (ex) {
4687 					// IE will set it to null
4688 					target[expando] = null;
4689 				}
4690 			}
4691 
4692 			return self;
4693 		};
4694 
4695 		self.fire = function(target, name, args) {
4696 			var id, event;
4697 
4698 			// Don't bind to text nodes or comments
4699 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4700 				return self;
4701 			}
4702 
4703 			// Build event object by patching the args
4704 			event = fix(null, args);
4705 			event.type = name;
4706 
4707 			do {
4708 				// Found an expando that means there is listeners to execute
4709 				id = target[expando];
4710 				if (id) {
4711 					executeHandlers(event, id);
4712 				}
4713 
4714 				// Walk up the DOM
4715 				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4716 			} while (target && !event.isPropagationStopped());
4717 
4718 			return self;
4719 		};
4720 
4721 		self.clean = function(target) {
4722 			var i, children, unbind = self.unbind;
4723 	
4724 			// Don't bind to text nodes or comments
4725 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4726 				return self;
4727 			}
4728 
4729 			// Unbind any element on the specificed target
4730 			if (target[expando]) {
4731 				unbind(target);
4732 			}
4733 
4734 			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
4735 			if (!target.getElementsByTagName) {
4736 				target = target.document;
4737 			}
4738 
4739 			// Remove events from each child element
4740 			if (target && target.getElementsByTagName) {
4741 				unbind(target);
4742 
4743 				children = target.getElementsByTagName('*');
4744 				i = children.length;
4745 				while (i--) {
4746 					target = children[i];
4747 
4748 					if (target[expando]) {
4749 						unbind(target);
4750 					}
4751 				}
4752 			}
4753 
4754 			return self;
4755 		};
4756 
4757 		self.callNativeHandler = function(id, evt) {
4758 			if (events) {
4759 				events[id][evt.type].nativeHandler(evt);
4760 			}
4761 		};
4762 
4763 		self.destory = function() {
4764 			events = {};
4765 		};
4766 
4767 		// Legacy function calls
4768 
4769 		self.add = function(target, events, func, scope) {
4770 			// Old API supported direct ID assignment
4771 			if (typeof(target) === "string") {
4772 				target = document.getElementById(target);
4773 			}
4774 
4775 			// Old API supported multiple targets
4776 			if (target && target instanceof Array) {
4777 				var i = target.length;
4778 
4779 				while (i--) {
4780 					self.add(target[i], events, func, scope);
4781 				}
4782 
4783 				return;
4784 			}
4785 
4786 			// Old API called ready init
4787 			if (events === "init") {
4788 				events = "ready";
4789 			}
4790 
4791 			return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
4792 		};
4793 
4794 		self.remove = function(target, events, func, scope) {
4795 			if (!target) {
4796 				return self;
4797 			}
4798 
4799 			// Old API supported direct ID assignment
4800 			if (typeof(target) === "string") {
4801 				target = document.getElementById(target);
4802 			}
4803 
4804 			// Old API supported multiple targets
4805 			if (target instanceof Array) {
4806 				var i = target.length;
4807 
4808 				while (i--) {
4809 					self.remove(target[i], events, func, scope);
4810 				}
4811 
4812 				return self;
4813 			}
4814 
4815 			return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
4816 		};
4817 
4818 		self.clear = function(target) {
4819 			// Old API supported direct ID assignment
4820 			if (typeof(target) === "string") {
4821 				target = document.getElementById(target);
4822 			}
4823 
4824 			return self.clean(target);
4825 		};
4826 
4827 		self.cancel = function(e) {
4828 			if (e) {
4829 				self.prevent(e);
4830 				self.stop(e);
4831 			}
4832 
4833 			return false;
4834 		};
4835 
4836 		self.prevent = function(e) {
4837 			if (!e.preventDefault) {
4838 				e = fix(e);
4839 			}
4840 
4841 			e.preventDefault();
4842 
4843 			return false;
4844 		};
4845 
4846 		self.stop = function(e) {
4847 			if (!e.stopPropagation) {
4848 				e = fix(e);
4849 			}
4850 
4851 			e.stopPropagation();
4852 
4853 			return false;
4854 		};
4855 	}
4856 
4857 	namespace.EventUtils = EventUtils;
4858 
4859 	namespace.Event = new EventUtils(function(id) {
4860 		return function(evt) {
4861 			tinymce.dom.Event.callNativeHandler(id, evt);
4862 		};
4863 	});
4864 
4865 	// Bind ready event when tinymce script is loaded
4866 	namespace.Event.bind(window, 'ready', function() {});
4867 
4868 	namespace = 0;
4869 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
4870 
4871 tinymce.dom.TreeWalker = function(start_node, root_node) {
4872 	var node = start_node;
4873 
4874 	function findSibling(node, start_name, sibling_name, shallow) {
4875 		var sibling, parent;
4876 
4877 		if (node) {
4878 			// Walk into nodes if it has a start
4879 			if (!shallow && node[start_name])
4880 				return node[start_name];
4881 
4882 			// Return the sibling if it has one
4883 			if (node != root_node) {
4884 				sibling = node[sibling_name];
4885 				if (sibling)
4886 					return sibling;
4887 
4888 				// Walk up the parents to look for siblings
4889 				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
4890 					sibling = parent[sibling_name];
4891 					if (sibling)
4892 						return sibling;
4893 				}
4894 			}
4895 		}
4896 	};
4897 
4898 	this.current = function() {
4899 		return node;
4900 	};
4901 
4902 	this.next = function(shallow) {
4903 		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
4904 	};
4905 
4906 	this.prev = function(shallow) {
4907 		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
4908 	};
4909 };
4910 
4911 (function(tinymce) {
4912 	// Shorten names
4913 	var each = tinymce.each,
4914 		is = tinymce.is,
4915 		isWebKit = tinymce.isWebKit,
4916 		isIE = tinymce.isIE,
4917 		Entities = tinymce.html.Entities,
4918 		simpleSelectorRe = /^([a-z0-9],?)+$/i,
4919 		whiteSpaceRegExp = /^[ \t\r\n]*$/;
4920 
4921 	tinymce.create('tinymce.dom.DOMUtils', {
4922 		doc : null,
4923 		root : null,
4924 		files : null,
4925 		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
4926 		props : {
4927 			"for" : "htmlFor",
4928 			"class" : "className",
4929 			className : "className",
4930 			checked : "checked",
4931 			disabled : "disabled",
4932 			maxlength : "maxLength",
4933 			readonly : "readOnly",
4934 			selected : "selected",
4935 			value : "value",
4936 			id : "id",
4937 			name : "name",
4938 			type : "type"
4939 		},
4940 
4941 		DOMUtils : function(d, s) {
4942 			var t = this, globalStyle, name, blockElementsMap;
4943 
4944 			t.doc = d;
4945 			t.win = window;
4946 			t.files = {};
4947 			t.cssFlicker = false;
4948 			t.counter = 0;
4949 			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
4950 			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
4951 			t.hasOuterHTML = "outerHTML" in d.createElement("a");
4952 
4953 			t.settings = s = tinymce.extend({
4954 				keep_values : false,
4955 				hex_colors : 1
4956 			}, s);
4957 			
4958 			t.schema = s.schema;
4959 			t.styles = new tinymce.html.Styles({
4960 				url_converter : s.url_converter,
4961 				url_converter_scope : s.url_converter_scope
4962 			}, s.schema);
4963 
4964 			// Fix IE6SP2 flicker and check it failed for pre SP2
4965 			if (tinymce.isIE6) {
4966 				try {
4967 					d.execCommand('BackgroundImageCache', false, true);
4968 				} catch (e) {
4969 					t.cssFlicker = true;
4970 				}
4971 			}
4972 
4973 			t.fixDoc(d);
4974 			t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
4975 			tinymce.addUnload(t.destroy, t);
4976 			blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
4977 
4978 			t.isBlock = function(node) {
4979 				// This function is called in module pattern style since it might be executed with the wrong this scope
4980 				var type = node.nodeType;
4981 
4982 				// If it's a node then check the type and use the nodeName
4983 				if (type)
4984 					return !!(type === 1 && blockElementsMap[node.nodeName]);
4985 
4986 				return !!blockElementsMap[node];
4987 			};
4988 		},
4989 
4990 		fixDoc: function(doc) {
4991 			var settings = this.settings, name;
4992 
4993 			if (isIE && settings.schema) {
4994 				// Add missing HTML 4/5 elements to IE
4995 				('abbr article aside audio canvas ' +
4996 				'details figcaption figure footer ' +
4997 				'header hgroup mark menu meter nav ' +
4998 				'output progress section summary ' +
4999 				'time video').replace(/\w+/g, function(name) {
5000 					doc.createElement(name);
5001 				});
5002 
5003 				// Create all custom elements
5004 				for (name in settings.schema.getCustomElements()) {
5005 					doc.createElement(name);
5006 				}
5007 			}
5008 		},
5009 
5010 		clone: function(node, deep) {
5011 			var self = this, clone, doc;
5012 
5013 			// TODO: Add feature detection here in the future
5014 			if (!isIE || node.nodeType !== 1 || deep) {
5015 				return node.cloneNode(deep);
5016 			}
5017 
5018 			doc = self.doc;
5019 
5020 			// Make a HTML5 safe shallow copy
5021 			if (!deep) {
5022 				clone = doc.createElement(node.nodeName);
5023 
5024 				// Copy attribs
5025 				each(self.getAttribs(node), function(attr) {
5026 					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5027 				});
5028 
5029 				return clone;
5030 			}
5031 /*
5032 			// Setup HTML5 patched document fragment
5033 			if (!self.frag) {
5034 				self.frag = doc.createDocumentFragment();
5035 				self.fixDoc(self.frag);
5036 			}
5037 
5038 			// Make a deep copy by adding it to the document fragment then removing it this removed the :section
5039 			clone = doc.createElement('div');
5040 			self.frag.appendChild(clone);
5041 			clone.innerHTML = node.outerHTML;
5042 			self.frag.removeChild(clone);
5043 */
5044 			return clone.firstChild;
5045 		},
5046 
5047 		getRoot : function() {
5048 			var t = this, s = t.settings;
5049 
5050 			return (s && t.get(s.root_element)) || t.doc.body;
5051 		},
5052 
5053 		getViewPort : function(w) {
5054 			var d, b;
5055 
5056 			w = !w ? this.win : w;
5057 			d = w.document;
5058 			b = this.boxModel ? d.documentElement : d.body;
5059 
5060 			// Returns viewport size excluding scrollbars
5061 			return {
5062 				x : w.pageXOffset || b.scrollLeft,
5063 				y : w.pageYOffset || b.scrollTop,
5064 				w : w.innerWidth || b.clientWidth,
5065 				h : w.innerHeight || b.clientHeight
5066 			};
5067 		},
5068 
5069 		getRect : function(e) {
5070 			var p, t = this, sr;
5071 
5072 			e = t.get(e);
5073 			p = t.getPos(e);
5074 			sr = t.getSize(e);
5075 
5076 			return {
5077 				x : p.x,
5078 				y : p.y,
5079 				w : sr.w,
5080 				h : sr.h
5081 			};
5082 		},
5083 
5084 		getSize : function(e) {
5085 			var t = this, w, h;
5086 
5087 			e = t.get(e);
5088 			w = t.getStyle(e, 'width');
5089 			h = t.getStyle(e, 'height');
5090 
5091 			// Non pixel value, then force offset/clientWidth
5092 			if (w.indexOf('px') === -1)
5093 				w = 0;
5094 
5095 			// Non pixel value, then force offset/clientWidth
5096 			if (h.indexOf('px') === -1)
5097 				h = 0;
5098 
5099 			return {
5100 				w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5101 				h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5102 			};
5103 		},
5104 
5105 		getParent : function(n, f, r) {
5106 			return this.getParents(n, f, r, false);
5107 		},
5108 
5109 		getParents : function(n, f, r, c) {
5110 			var t = this, na, se = t.settings, o = [];
5111 
5112 			n = t.get(n);
5113 			c = c === undefined;
5114 
5115 			if (se.strict_root)
5116 				r = r || t.getRoot();
5117 
5118 			// Wrap node name as func
5119 			if (is(f, 'string')) {
5120 				na = f;
5121 
5122 				if (f === '*') {
5123 					f = function(n) {return n.nodeType == 1;};
5124 				} else {
5125 					f = function(n) {
5126 						return t.is(n, na);
5127 					};
5128 				}
5129 			}
5130 
5131 			while (n) {
5132 				if (n == r || !n.nodeType || n.nodeType === 9)
5133 					break;
5134 
5135 				if (!f || f(n)) {
5136 					if (c)
5137 						o.push(n);
5138 					else
5139 						return n;
5140 				}
5141 
5142 				n = n.parentNode;
5143 			}
5144 
5145 			return c ? o : null;
5146 		},
5147 
5148 		get : function(e) {
5149 			var n;
5150 
5151 			if (e && this.doc && typeof(e) == 'string') {
5152 				n = e;
5153 				e = this.doc.getElementById(e);
5154 
5155 				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5156 				if (e && e.id !== n)
5157 					return this.doc.getElementsByName(n)[1];
5158 			}
5159 
5160 			return e;
5161 		},
5162 
5163 		getNext : function(node, selector) {
5164 			return this._findSib(node, selector, 'nextSibling');
5165 		},
5166 
5167 		getPrev : function(node, selector) {
5168 			return this._findSib(node, selector, 'previousSibling');
5169 		},
5170 
5171 
5172 		select : function(pa, s) {
5173 			var t = this;
5174 
5175 			return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
5176 		},
5177 
5178 		is : function(n, selector) {
5179 			var i;
5180 
5181 			// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5182 			if (n.length === undefined) {
5183 				// Simple all selector
5184 				if (selector === '*')
5185 					return n.nodeType == 1;
5186 
5187 				// Simple selector just elements
5188 				if (simpleSelectorRe.test(selector)) {
5189 					selector = selector.toLowerCase().split(/,/);
5190 					n = n.nodeName.toLowerCase();
5191 
5192 					for (i = selector.length - 1; i >= 0; i--) {
5193 						if (selector[i] == n)
5194 							return true;
5195 					}
5196 
5197 					return false;
5198 				}
5199 			}
5200 
5201 			return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
5202 		},
5203 
5204 
5205 		add : function(p, n, a, h, c) {
5206 			var t = this;
5207 
5208 			return this.run(p, function(p) {
5209 				var e, k;
5210 
5211 				e = is(n, 'string') ? t.doc.createElement(n) : n;
5212 				t.setAttribs(e, a);
5213 
5214 				if (h) {
5215 					if (h.nodeType)
5216 						e.appendChild(h);
5217 					else
5218 						t.setHTML(e, h);
5219 				}
5220 
5221 				return !c ? p.appendChild(e) : e;
5222 			});
5223 		},
5224 
5225 		create : function(n, a, h) {
5226 			return this.add(this.doc.createElement(n), n, a, h, 1);
5227 		},
5228 
5229 		createHTML : function(n, a, h) {
5230 			var o = '', t = this, k;
5231 
5232 			o += '<' + n;
5233 
5234 			for (k in a) {
5235 				if (a.hasOwnProperty(k))
5236 					o += ' ' + k + '="' + t.encode(a[k]) + '"';
5237 			}
5238 
5239 			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5240 			if (typeof(h) != "undefined")
5241 				return o + '>' + h + '</' + n + '>';
5242 
5243 			return o + ' />';
5244 		},
5245 
5246 		remove : function(node, keep_children) {
5247 			return this.run(node, function(node) {
5248 				var child, parent = node.parentNode;
5249 
5250 				if (!parent)
5251 					return null;
5252 
5253 				if (keep_children) {
5254 					while (child = node.firstChild) {
5255 						// IE 8 will crash if you don't remove completely empty text nodes
5256 						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5257 							parent.insertBefore(child, node);
5258 						else
5259 							node.removeChild(child);
5260 					}
5261 				}
5262 
5263 				return parent.removeChild(node);
5264 			});
5265 		},
5266 
5267 		setStyle : function(n, na, v) {
5268 			var t = this;
5269 
5270 			return t.run(n, function(e) {
5271 				var s, i;
5272 
5273 				s = e.style;
5274 
5275 				// Camelcase it, if needed
5276 				na = na.replace(/-(\D)/g, function(a, b){
5277 					return b.toUpperCase();
5278 				});
5279 
5280 				// Default px suffix on these
5281 				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5282 					v += 'px';
5283 
5284 				switch (na) {
5285 					case 'opacity':
5286 						// IE specific opacity
5287 						if (isIE) {
5288 							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5289 
5290 							if (!n.currentStyle || !n.currentStyle.hasLayout)
5291 								s.display = 'inline-block';
5292 						}
5293 
5294 						// Fix for older browsers
5295 						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5296 						break;
5297 
5298 					case 'float':
5299 						isIE ? s.styleFloat = v : s.cssFloat = v;
5300 						break;
5301 					
5302 					default:
5303 						s[na] = v || '';
5304 				}
5305 
5306 				// Force update of the style data
5307 				if (t.settings.update_styles)
5308 					t.setAttrib(e, 'data-mce-style');
5309 			});
5310 		},
5311 
5312 		getStyle : function(n, na, c) {
5313 			n = this.get(n);
5314 
5315 			if (!n)
5316 				return;
5317 
5318 			// Gecko
5319 			if (this.doc.defaultView && c) {
5320 				// Remove camelcase
5321 				na = na.replace(/[A-Z]/g, function(a){
5322 					return '-' + a;
5323 				});
5324 
5325 				try {
5326 					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5327 				} catch (ex) {
5328 					// Old safari might fail
5329 					return null;
5330 				}
5331 			}
5332 
5333 			// Camelcase it, if needed
5334 			na = na.replace(/-(\D)/g, function(a, b){
5335 				return b.toUpperCase();
5336 			});
5337 
5338 			if (na == 'float')
5339 				na = isIE ? 'styleFloat' : 'cssFloat';
5340 
5341 			// IE & Opera
5342 			if (n.currentStyle && c)
5343 				return n.currentStyle[na];
5344 
5345 			return n.style ? n.style[na] : undefined;
5346 		},
5347 
5348 		setStyles : function(e, o) {
5349 			var t = this, s = t.settings, ol;
5350 
5351 			ol = s.update_styles;
5352 			s.update_styles = 0;
5353 
5354 			each(o, function(v, n) {
5355 				t.setStyle(e, n, v);
5356 			});
5357 
5358 			// Update style info
5359 			s.update_styles = ol;
5360 			if (s.update_styles)
5361 				t.setAttrib(e, s.cssText);
5362 		},
5363 
5364 		removeAllAttribs: function(e) {
5365 			return this.run(e, function(e) {
5366 				var i, attrs = e.attributes;
5367 				for (i = attrs.length - 1; i >= 0; i--) {
5368 					e.removeAttributeNode(attrs.item(i));
5369 				}
5370 			});
5371 		},
5372 
5373 		setAttrib : function(e, n, v) {
5374 			var t = this;
5375 
5376 			// Whats the point
5377 			if (!e || !n)
5378 				return;
5379 
5380 			// Strict XML mode
5381 			if (t.settings.strict)
5382 				n = n.toLowerCase();
5383 
5384 			return this.run(e, function(e) {
5385 				var s = t.settings;
5386 				var originalValue = e.getAttribute(n);
5387 				if (v !== null) {
5388 					switch (n) {
5389 						case "style":
5390 							if (!is(v, 'string')) {
5391 								each(v, function(v, n) {
5392 									t.setStyle(e, n, v);
5393 								});
5394 
5395 								return;
5396 							}
5397 
5398 							// No mce_style for elements with these since they might get resized by the user
5399 							if (s.keep_values) {
5400 								if (v && !t._isRes(v))
5401 									e.setAttribute('data-mce-style', v, 2);
5402 								else
5403 									e.removeAttribute('data-mce-style', 2);
5404 							}
5405 
5406 							e.style.cssText = v;
5407 							break;
5408 
5409 						case "class":
5410 							e.className = v || ''; // Fix IE null bug
5411 							break;
5412 
5413 						case "src":
5414 						case "href":
5415 							if (s.keep_values) {
5416 								if (s.url_converter)
5417 									v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5418 
5419 								t.setAttrib(e, 'data-mce-' + n, v, 2);
5420 							}
5421 
5422 							break;
5423 
5424 						case "shape":
5425 							e.setAttribute('data-mce-style', v);
5426 							break;
5427 					}
5428 				}
5429 				if (is(v) && v !== null && v.length !== 0)
5430 					e.setAttribute(n, '' + v, 2);
5431 				else
5432 					e.removeAttribute(n, 2);
5433 
5434 				// fire onChangeAttrib event for attributes that have changed
5435 				if (tinyMCE.activeEditor && originalValue != v) {
5436 					var ed = tinyMCE.activeEditor;
5437 					ed.onSetAttrib.dispatch(ed, e, n, v);
5438 				}
5439 			});
5440 		},
5441 
5442 		setAttribs : function(e, o) {
5443 			var t = this;
5444 
5445 			return this.run(e, function(e) {
5446 				each(o, function(v, n) {
5447 					t.setAttrib(e, n, v);
5448 				});
5449 			});
5450 		},
5451 
5452 		getAttrib : function(e, n, dv) {
5453 			var v, t = this, undef;
5454 
5455 			e = t.get(e);
5456 
5457 			if (!e || e.nodeType !== 1)
5458 				return dv === undef ? false : dv;
5459 
5460 			if (!is(dv))
5461 				dv = '';
5462 
5463 			// Try the mce variant for these
5464 			if (/^(src|href|style|coords|shape)$/.test(n)) {
5465 				v = e.getAttribute("data-mce-" + n);
5466 
5467 				if (v)
5468 					return v;
5469 			}
5470 
5471 			if (isIE && t.props[n]) {
5472 				v = e[t.props[n]];
5473 				v = v && v.nodeValue ? v.nodeValue : v;
5474 			}
5475 
5476 			if (!v)
5477 				v = e.getAttribute(n, 2);
5478 
5479 			// Check boolean attribs
5480 			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5481 				if (e[t.props[n]] === true && v === '')
5482 					return n;
5483 
5484 				return v ? n : '';
5485 			}
5486 
5487 			// Inner input elements will override attributes on form elements
5488 			if (e.nodeName === "FORM" && e.getAttributeNode(n))
5489 				return e.getAttributeNode(n).nodeValue;
5490 
5491 			if (n === 'style') {
5492 				v = v || e.style.cssText;
5493 
5494 				if (v) {
5495 					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5496 
5497 					if (t.settings.keep_values && !t._isRes(v))
5498 						e.setAttribute('data-mce-style', v);
5499 				}
5500 			}
5501 
5502 			// Remove Apple and WebKit stuff
5503 			if (isWebKit && n === "class" && v)
5504 				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5505 
5506 			// Handle IE issues
5507 			if (isIE) {
5508 				switch (n) {
5509 					case 'rowspan':
5510 					case 'colspan':
5511 						// IE returns 1 as default value
5512 						if (v === 1)
5513 							v = '';
5514 
5515 						break;
5516 
5517 					case 'size':
5518 						// IE returns +0 as default value for size
5519 						if (v === '+0' || v === 20 || v === 0)
5520 							v = '';
5521 
5522 						break;
5523 
5524 					case 'width':
5525 					case 'height':
5526 					case 'vspace':
5527 					case 'checked':
5528 					case 'disabled':
5529 					case 'readonly':
5530 						if (v === 0)
5531 							v = '';
5532 
5533 						break;
5534 
5535 					case 'hspace':
5536 						// IE returns -1 as default value
5537 						if (v === -1)
5538 							v = '';
5539 
5540 						break;
5541 
5542 					case 'maxlength':
5543 					case 'tabindex':
5544 						// IE returns default value
5545 						if (v === 32768 || v === 2147483647 || v === '32768')
5546 							v = '';
5547 
5548 						break;
5549 
5550 					case 'multiple':
5551 					case 'compact':
5552 					case 'noshade':
5553 					case 'nowrap':
5554 						if (v === 65535)
5555 							return n;
5556 
5557 						return dv;
5558 
5559 					case 'shape':
5560 						v = v.toLowerCase();
5561 						break;
5562 
5563 					default:
5564 						// IE has odd anonymous function for event attributes
5565 						if (n.indexOf('on') === 0 && v)
5566 							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5567 				}
5568 			}
5569 
5570 			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5571 		},
5572 
5573 		getPos : function(n, ro) {
5574 			var t = this, x = 0, y = 0, e, d = t.doc, r;
5575 
5576 			n = t.get(n);
5577 			ro = ro || d.body;
5578 
5579 			if (n) {
5580 				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5581 				if (n.getBoundingClientRect) {
5582 					n = n.getBoundingClientRect();
5583 					e = t.boxModel ? d.documentElement : d.body;
5584 
5585 					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5586 					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5587 					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5588 					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5589 
5590 					return {x : x, y : y};
5591 				}
5592 
5593 				r = n;
5594 				while (r && r != ro && r.nodeType) {
5595 					x += r.offsetLeft || 0;
5596 					y += r.offsetTop || 0;
5597 					r = r.offsetParent;
5598 				}
5599 
5600 				r = n.parentNode;
5601 				while (r && r != ro && r.nodeType) {
5602 					x -= r.scrollLeft || 0;
5603 					y -= r.scrollTop || 0;
5604 					r = r.parentNode;
5605 				}
5606 			}
5607 
5608 			return {x : x, y : y};
5609 		},
5610 
5611 		parseStyle : function(st) {
5612 			return this.styles.parse(st);
5613 		},
5614 
5615 		serializeStyle : function(o, name) {
5616 			return this.styles.serialize(o, name);
5617 		},
5618 
5619 		addStyle: function(cssText) {
5620 			var doc = this.doc, head;
5621 
5622 			// Create style element if needed
5623 			styleElm = doc.getElementById('mceDefaultStyles');
5624 			if (!styleElm) {
5625 				styleElm = doc.createElement('style'),
5626 				styleElm.id = 'mceDefaultStyles';
5627 				styleElm.type = 'text/css';
5628 
5629 				head = doc.getElementsByTagName('head')[0]
5630 				if (head.firstChild) {
5631 					head.insertBefore(styleElm, head.firstChild);
5632 				} else {
5633 					head.appendChild(styleElm);
5634 				}
5635 			}
5636 
5637 			// Append style data to old or new style element
5638 			if (styleElm.styleSheet) {
5639 				styleElm.styleSheet.cssText += cssText;
5640 			} else {
5641 				styleElm.appendChild(doc.createTextNode(cssText));
5642 			}
5643 		},
5644 
5645 		loadCSS : function(u) {
5646 			var t = this, d = t.doc, head;
5647 
5648 			if (!u)
5649 				u = '';
5650 
5651 			head = d.getElementsByTagName('head')[0];
5652 
5653 			each(u.split(','), function(u) {
5654 				var link;
5655 
5656 				if (t.files[u])
5657 					return;
5658 
5659 				t.files[u] = true;
5660 				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5661 
5662 				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5663 				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5664 				// It's ugly but it seems to work fine.
5665 				if (isIE && d.documentMode && d.recalc) {
5666 					link.onload = function() {
5667 						if (d.recalc)
5668 							d.recalc();
5669 
5670 						link.onload = null;
5671 					};
5672 				}
5673 
5674 				head.appendChild(link);
5675 			});
5676 		},
5677 
5678 		addClass : function(e, c) {
5679 			return this.run(e, function(e) {
5680 				var o;
5681 
5682 				if (!c)
5683 					return 0;
5684 
5685 				if (this.hasClass(e, c))
5686 					return e.className;
5687 
5688 				o = this.removeClass(e, c);
5689 
5690 				return e.className = (o != '' ? (o + ' ') : '') + c;
5691 			});
5692 		},
5693 
5694 		removeClass : function(e, c) {
5695 			var t = this, re;
5696 
5697 			return t.run(e, function(e) {
5698 				var v;
5699 
5700 				if (t.hasClass(e, c)) {
5701 					if (!re)
5702 						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5703 
5704 					v = e.className.replace(re, ' ');
5705 					v = tinymce.trim(v != ' ' ? v : '');
5706 
5707 					e.className = v;
5708 
5709 					// Empty class attr
5710 					if (!v) {
5711 						e.removeAttribute('class');
5712 						e.removeAttribute('className');
5713 					}
5714 
5715 					return v;
5716 				}
5717 
5718 				return e.className;
5719 			});
5720 		},
5721 
5722 		hasClass : function(n, c) {
5723 			n = this.get(n);
5724 
5725 			if (!n || !c)
5726 				return false;
5727 
5728 			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5729 		},
5730 
5731 		show : function(e) {
5732 			return this.setStyle(e, 'display', 'block');
5733 		},
5734 
5735 		hide : function(e) {
5736 			return this.setStyle(e, 'display', 'none');
5737 		},
5738 
5739 		isHidden : function(e) {
5740 			e = this.get(e);
5741 
5742 			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
5743 		},
5744 
5745 		uniqueId : function(p) {
5746 			return (!p ? 'mce_' : p) + (this.counter++);
5747 		},
5748 
5749 		setHTML : function(element, html) {
5750 			var self = this;
5751 
5752 			return self.run(element, function(element) {
5753 				if (isIE) {
5754 					// Remove all child nodes, IE keeps empty text nodes in DOM
5755 					while (element.firstChild)
5756 						element.removeChild(element.firstChild);
5757 
5758 					try {
5759 						// IE will remove comments from the beginning
5760 						// unless you padd the contents with something
5761 						element.innerHTML = '<br />' + html;
5762 						element.removeChild(element.firstChild);
5763 					} catch (ex) {
5764 						// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
5765 						// This seems to fix this problem
5766 
5767 						// Create new div with HTML contents and a BR infront to keep comments
5768 						var newElement = self.create('div');
5769 						newElement.innerHTML = '<br />' + html;
5770 
5771 						// Add all children from div to target
5772 						each (tinymce.grep(newElement.childNodes), function(node, i) {
5773 							// Skip br element
5774 							if (i && element.canHaveHTML)
5775 								element.appendChild(node);
5776 						});
5777 					}
5778 				} else
5779 					element.innerHTML = html;
5780 
5781 				return html;
5782 			});
5783 		},
5784 
5785 		getOuterHTML : function(elm) {
5786 			var doc, self = this;
5787 
5788 			elm = self.get(elm);
5789 
5790 			if (!elm)
5791 				return null;
5792 
5793 			if (elm.nodeType === 1 && self.hasOuterHTML)
5794 				return elm.outerHTML;
5795 
5796 			doc = (elm.ownerDocument || self.doc).createElement("body");
5797 			doc.appendChild(elm.cloneNode(true));
5798 
5799 			return doc.innerHTML;
5800 		},
5801 
5802 		setOuterHTML : function(e, h, d) {
5803 			var t = this;
5804 
5805 			function setHTML(e, h, d) {
5806 				var n, tp;
5807 
5808 				tp = d.createElement("body");
5809 				tp.innerHTML = h;
5810 
5811 				n = tp.lastChild;
5812 				while (n) {
5813 					t.insertAfter(n.cloneNode(true), e);
5814 					n = n.previousSibling;
5815 				}
5816 
5817 				t.remove(e);
5818 			};
5819 
5820 			return this.run(e, function(e) {
5821 				e = t.get(e);
5822 
5823 				// Only set HTML on elements
5824 				if (e.nodeType == 1) {
5825 					d = d || e.ownerDocument || t.doc;
5826 
5827 					if (isIE) {
5828 						try {
5829 							// Try outerHTML for IE it sometimes produces an unknown runtime error
5830 							if (isIE && e.nodeType == 1)
5831 								e.outerHTML = h;
5832 							else
5833 								setHTML(e, h, d);
5834 						} catch (ex) {
5835 							// Fix for unknown runtime error
5836 							setHTML(e, h, d);
5837 						}
5838 					} else
5839 						setHTML(e, h, d);
5840 				}
5841 			});
5842 		},
5843 
5844 		decode : Entities.decode,
5845 
5846 		encode : Entities.encodeAllRaw,
5847 
5848 		insertAfter : function(node, reference_node) {
5849 			reference_node = this.get(reference_node);
5850 
5851 			return this.run(node, function(node) {
5852 				var parent, nextSibling;
5853 
5854 				parent = reference_node.parentNode;
5855 				nextSibling = reference_node.nextSibling;
5856 
5857 				if (nextSibling)
5858 					parent.insertBefore(node, nextSibling);
5859 				else
5860 					parent.appendChild(node);
5861 
5862 				return node;
5863 			});
5864 		},
5865 
5866 		replace : function(n, o, k) {
5867 			var t = this;
5868 
5869 			if (is(o, 'array'))
5870 				n = n.cloneNode(true);
5871 
5872 			return t.run(o, function(o) {
5873 				if (k) {
5874 					each(tinymce.grep(o.childNodes), function(c) {
5875 						n.appendChild(c);
5876 					});
5877 				}
5878 
5879 				return o.parentNode.replaceChild(n, o);
5880 			});
5881 		},
5882 
5883 		rename : function(elm, name) {
5884 			var t = this, newElm;
5885 
5886 			if (elm.nodeName != name.toUpperCase()) {
5887 				// Rename block element
5888 				newElm = t.create(name);
5889 
5890 				// Copy attribs to new block
5891 				each(t.getAttribs(elm), function(attr_node) {
5892 					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
5893 				});
5894 
5895 				// Replace block
5896 				t.replace(newElm, elm, 1);
5897 			}
5898 
5899 			return newElm || elm;
5900 		},
5901 
5902 		findCommonAncestor : function(a, b) {
5903 			var ps = a, pe;
5904 
5905 			while (ps) {
5906 				pe = b;
5907 
5908 				while (pe && ps != pe)
5909 					pe = pe.parentNode;
5910 
5911 				if (ps == pe)
5912 					break;
5913 
5914 				ps = ps.parentNode;
5915 			}
5916 
5917 			if (!ps && a.ownerDocument)
5918 				return a.ownerDocument.documentElement;
5919 
5920 			return ps;
5921 		},
5922 
5923 		toHex : function(s) {
5924 			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
5925 
5926 			function hex(s) {
5927 				s = parseInt(s, 10).toString(16);
5928 
5929 				return s.length > 1 ? s : '0' + s; // 0 -> 00
5930 			};
5931 
5932 			if (c) {
5933 				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
5934 
5935 				return s;
5936 			}
5937 
5938 			return s;
5939 		},
5940 
5941 		getClasses : function() {
5942 			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
5943 
5944 			if (t.classes)
5945 				return t.classes;
5946 
5947 			function addClasses(s) {
5948 				// IE style imports
5949 				each(s.imports, function(r) {
5950 					addClasses(r);
5951 				});
5952 
5953 				each(s.cssRules || s.rules, function(r) {
5954 					// Real type or fake it on IE
5955 					switch (r.type || 1) {
5956 						// Rule
5957 						case 1:
5958 							if (r.selectorText) {
5959 								each(r.selectorText.split(','), function(v) {
5960 									v = v.replace(/^\s*|\s*$|^\s\./g, "");
5961 
5962 									// Is internal or it doesn't contain a class
5963 									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
5964 										return;
5965 
5966 									// Remove everything but class name
5967 									ov = v;
5968 									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
5969 
5970 									// Filter classes
5971 									if (f && !(v = f(v, ov)))
5972 										return;
5973 
5974 									if (!lo[v]) {
5975 										cl.push({'class' : v});
5976 										lo[v] = 1;
5977 									}
5978 								});
5979 							}
5980 							break;
5981 
5982 						// Import
5983 						case 3:
5984 							addClasses(r.styleSheet);
5985 							break;
5986 					}
5987 				});
5988 			};
5989 
5990 			try {
5991 				each(t.doc.styleSheets, addClasses);
5992 			} catch (ex) {
5993 				// Ignore
5994 			}
5995 
5996 			if (cl.length > 0)
5997 				t.classes = cl;
5998 
5999 			return cl;
6000 		},
6001 
6002 		run : function(e, f, s) {
6003 			var t = this, o;
6004 
6005 			if (t.doc && typeof(e) === 'string')
6006 				e = t.get(e);
6007 
6008 			if (!e)
6009 				return false;
6010 
6011 			s = s || this;
6012 			if (!e.nodeType && (e.length || e.length === 0)) {
6013 				o = [];
6014 
6015 				each(e, function(e, i) {
6016 					if (e) {
6017 						if (typeof(e) == 'string')
6018 							e = t.doc.getElementById(e);
6019 
6020 						o.push(f.call(s, e, i));
6021 					}
6022 				});
6023 
6024 				return o;
6025 			}
6026 
6027 			return f.call(s, e);
6028 		},
6029 
6030 		getAttribs : function(n) {
6031 			var o;
6032 
6033 			n = this.get(n);
6034 
6035 			if (!n)
6036 				return [];
6037 
6038 			if (isIE) {
6039 				o = [];
6040 
6041 				// Object will throw exception in IE
6042 				if (n.nodeName == 'OBJECT')
6043 					return n.attributes;
6044 
6045 				// IE doesn't keep the selected attribute if you clone option elements
6046 				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6047 					o.push({specified : 1, nodeName : 'selected'});
6048 
6049 				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6050 				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6051 					o.push({specified : 1, nodeName : a});
6052 				});
6053 
6054 				return o;
6055 			}
6056 
6057 			return n.attributes;
6058 		},
6059 
6060 		isEmpty : function(node, elements) {
6061 			var self = this, i, attributes, type, walker, name, brCount = 0;
6062 
6063 			node = node.firstChild;
6064 			if (node) {
6065 				walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6066 				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6067 
6068 				do {
6069 					type = node.nodeType;
6070 
6071 					if (type === 1) {
6072 						// Ignore bogus elements
6073 						if (node.getAttribute('data-mce-bogus'))
6074 							continue;
6075 
6076 						// Keep empty elements like <img />
6077 						name = node.nodeName.toLowerCase();
6078 						if (elements && elements[name]) {
6079 							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6080 							if (name === 'br') {
6081 								brCount++;
6082 								continue;
6083 							}
6084 
6085 							return false;
6086 						}
6087 
6088 						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6089 						attributes = self.getAttribs(node);
6090 						i = node.attributes.length;
6091 						while (i--) {
6092 							name = node.attributes[i].nodeName;
6093 							if (name === "name" || name === 'data-mce-bookmark')
6094 								return false;
6095 						}
6096 					}
6097 
6098 					// Keep comment nodes
6099 					if (type == 8)
6100 						return false;
6101 
6102 					// Keep non whitespace text nodes
6103 					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6104 						return false;
6105 				} while (node = walker.next());
6106 			}
6107 
6108 			return brCount <= 1;
6109 		},
6110 
6111 		destroy : function(s) {
6112 			var t = this;
6113 
6114 			t.win = t.doc = t.root = t.events = t.frag = null;
6115 
6116 			// Manual destroy then remove unload handler
6117 			if (!s)
6118 				tinymce.removeUnload(t.destroy);
6119 		},
6120 
6121 		createRng : function() {
6122 			var d = this.doc;
6123 
6124 			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6125 		},
6126 
6127 		nodeIndex : function(node, normalized) {
6128 			var idx = 0, lastNodeType, lastNode, nodeType;
6129 
6130 			if (node) {
6131 				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6132 					nodeType = node.nodeType;
6133 
6134 					// Normalize text nodes
6135 					if (normalized && nodeType == 3) {
6136 						if (nodeType == lastNodeType || !node.nodeValue.length)
6137 							continue;
6138 					}
6139 					idx++;
6140 					lastNodeType = nodeType;
6141 				}
6142 			}
6143 
6144 			return idx;
6145 		},
6146 
6147 		split : function(pe, e, re) {
6148 			var t = this, r = t.createRng(), bef, aft, pa;
6149 
6150 			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6151 			// but we don't want that in our code since it serves no purpose for the end user
6152 			// For example if this is chopped:
6153 			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6154 			// would produce:
6155 			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6156 			// this function will then trim of empty edges and produce:
6157 			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6158 			function trim(node) {
6159 				var i, children = node.childNodes, type = node.nodeType;
6160 
6161 				function surroundedBySpans(node) {
6162 					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6163 					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6164 					return previousIsSpan && nextIsSpan;
6165 				}
6166 
6167 				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6168 					return;
6169 
6170 				for (i = children.length - 1; i >= 0; i--)
6171 					trim(children[i]);
6172 
6173 				if (type != 9) {
6174 					// Keep non whitespace text nodes
6175 					if (type == 3 && node.nodeValue.length > 0) {
6176 						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6177 						// Also keep text nodes with only spaces if surrounded by spans.
6178 						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6179 						var trimmedLength = tinymce.trim(node.nodeValue).length;
6180 						if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6181 							return;
6182 					} else if (type == 1) {
6183 						// If the only child is a bookmark then move it up
6184 						children = node.childNodes;
6185 						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6186 							node.parentNode.insertBefore(children[0], node);
6187 
6188 						// Keep non empty elements or img, hr etc
6189 						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6190 							return;
6191 					}
6192 
6193 					t.remove(node);
6194 				}
6195 
6196 				return node;
6197 			};
6198 
6199 			if (pe && e) {
6200 				// Get before chunk
6201 				r.setStart(pe.parentNode, t.nodeIndex(pe));
6202 				r.setEnd(e.parentNode, t.nodeIndex(e));
6203 				bef = r.extractContents();
6204 
6205 				// Get after chunk
6206 				r = t.createRng();
6207 				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6208 				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6209 				aft = r.extractContents();
6210 
6211 				// Insert before chunk
6212 				pa = pe.parentNode;
6213 				pa.insertBefore(trim(bef), pe);
6214 
6215 				// Insert middle chunk
6216 				if (re)
6217 				pa.replaceChild(re, e);
6218 			else
6219 				pa.insertBefore(e, pe);
6220 
6221 				// Insert after chunk
6222 				pa.insertBefore(trim(aft), pe);
6223 				t.remove(pe);
6224 
6225 				return re || e;
6226 			}
6227 		},
6228 
6229 		bind : function(target, name, func, scope) {
6230 			return this.events.add(target, name, func, scope || this);
6231 		},
6232 
6233 		unbind : function(target, name, func) {
6234 			return this.events.remove(target, name, func);
6235 		},
6236 
6237 		fire : function(target, name, evt) {
6238 			return this.events.fire(target, name, evt);
6239 		},
6240 
6241 		// Returns the content editable state of a node
6242 		getContentEditable: function(node) {
6243 			var contentEditable;
6244 
6245 			// Check type
6246 			if (node.nodeType != 1) {
6247 				return null;
6248 			}
6249 
6250 			// Check for fake content editable
6251 			contentEditable = node.getAttribute("data-mce-contenteditable");
6252 			if (contentEditable && contentEditable !== "inherit") {
6253 				return contentEditable;
6254 			}
6255 
6256 			// Check for real content editable
6257 			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6258 		},
6259 
6260 
6261 		_findSib : function(node, selector, name) {
6262 			var t = this, f = selector;
6263 
6264 			if (node) {
6265 				// If expression make a function of it using is
6266 				if (is(f, 'string')) {
6267 					f = function(node) {
6268 						return t.is(node, selector);
6269 					};
6270 				}
6271 
6272 				// Loop all siblings
6273 				for (node = node[name]; node; node = node[name]) {
6274 					if (f(node))
6275 						return node;
6276 				}
6277 			}
6278 
6279 			return null;
6280 		},
6281 
6282 		_isRes : function(c) {
6283 			// Is live resizble element
6284 			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6285 		}
6286 
6287 		/*
6288 		walk : function(n, f, s) {
6289 			var d = this.doc, w;
6290 
6291 			if (d.createTreeWalker) {
6292 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6293 
6294 				while ((n = w.nextNode()) != null)
6295 					f.call(s || this, n);
6296 			} else
6297 				tinymce.walk(n, f, 'childNodes', s);
6298 		}
6299 		*/
6300 
6301 		/*
6302 		toRGB : function(s) {
6303 			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6304 
6305 			if (c) {
6306 				// #FFF -> #FFFFFF
6307 				if (!is(c[3]))
6308 					c[3] = c[2] = c[1];
6309 
6310 				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6311 			}
6312 
6313 			return s;
6314 		}
6315 		*/
6316 	});
6317 
6318 	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6319 })(tinymce);
6320 
6321 (function(ns) {
6322 	// Range constructor
6323 	function Range(dom) {
6324 		var t = this,
6325 			doc = dom.doc,
6326 			EXTRACT = 0,
6327 			CLONE = 1,
6328 			DELETE = 2,
6329 			TRUE = true,
6330 			FALSE = false,
6331 			START_OFFSET = 'startOffset',
6332 			START_CONTAINER = 'startContainer',
6333 			END_CONTAINER = 'endContainer',
6334 			END_OFFSET = 'endOffset',
6335 			extend = tinymce.extend,
6336 			nodeIndex = dom.nodeIndex;
6337 
6338 		extend(t, {
6339 			// Inital states
6340 			startContainer : doc,
6341 			startOffset : 0,
6342 			endContainer : doc,
6343 			endOffset : 0,
6344 			collapsed : TRUE,
6345 			commonAncestorContainer : doc,
6346 
6347 			// Range constants
6348 			START_TO_START : 0,
6349 			START_TO_END : 1,
6350 			END_TO_END : 2,
6351 			END_TO_START : 3,
6352 
6353 			// Public methods
6354 			setStart : setStart,
6355 			setEnd : setEnd,
6356 			setStartBefore : setStartBefore,
6357 			setStartAfter : setStartAfter,
6358 			setEndBefore : setEndBefore,
6359 			setEndAfter : setEndAfter,
6360 			collapse : collapse,
6361 			selectNode : selectNode,
6362 			selectNodeContents : selectNodeContents,
6363 			compareBoundaryPoints : compareBoundaryPoints,
6364 			deleteContents : deleteContents,
6365 			extractContents : extractContents,
6366 			cloneContents : cloneContents,
6367 			insertNode : insertNode,
6368 			surroundContents : surroundContents,
6369 			cloneRange : cloneRange,
6370 			toStringIE : toStringIE
6371 		});
6372 
6373 		function createDocumentFragment() {
6374 			return doc.createDocumentFragment();
6375 		};
6376 
6377 		function setStart(n, o) {
6378 			_setEndPoint(TRUE, n, o);
6379 		};
6380 
6381 		function setEnd(n, o) {
6382 			_setEndPoint(FALSE, n, o);
6383 		};
6384 
6385 		function setStartBefore(n) {
6386 			setStart(n.parentNode, nodeIndex(n));
6387 		};
6388 
6389 		function setStartAfter(n) {
6390 			setStart(n.parentNode, nodeIndex(n) + 1);
6391 		};
6392 
6393 		function setEndBefore(n) {
6394 			setEnd(n.parentNode, nodeIndex(n));
6395 		};
6396 
6397 		function setEndAfter(n) {
6398 			setEnd(n.parentNode, nodeIndex(n) + 1);
6399 		};
6400 
6401 		function collapse(ts) {
6402 			if (ts) {
6403 				t[END_CONTAINER] = t[START_CONTAINER];
6404 				t[END_OFFSET] = t[START_OFFSET];
6405 			} else {
6406 				t[START_CONTAINER] = t[END_CONTAINER];
6407 				t[START_OFFSET] = t[END_OFFSET];
6408 			}
6409 
6410 			t.collapsed = TRUE;
6411 		};
6412 
6413 		function selectNode(n) {
6414 			setStartBefore(n);
6415 			setEndAfter(n);
6416 		};
6417 
6418 		function selectNodeContents(n) {
6419 			setStart(n, 0);
6420 			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6421 		};
6422 
6423 		function compareBoundaryPoints(h, r) {
6424 			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6425 			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6426 
6427 			// Check START_TO_START
6428 			if (h === 0)
6429 				return _compareBoundaryPoints(sc, so, rsc, rso);
6430 	
6431 			// Check START_TO_END
6432 			if (h === 1)
6433 				return _compareBoundaryPoints(ec, eo, rsc, rso);
6434 	
6435 			// Check END_TO_END
6436 			if (h === 2)
6437 				return _compareBoundaryPoints(ec, eo, rec, reo);
6438 	
6439 			// Check END_TO_START
6440 			if (h === 3) 
6441 				return _compareBoundaryPoints(sc, so, rec, reo);
6442 		};
6443 
6444 		function deleteContents() {
6445 			_traverse(DELETE);
6446 		};
6447 
6448 		function extractContents() {
6449 			return _traverse(EXTRACT);
6450 		};
6451 
6452 		function cloneContents() {
6453 			return _traverse(CLONE);
6454 		};
6455 
6456 		function insertNode(n) {
6457 			var startContainer = this[START_CONTAINER],
6458 				startOffset = this[START_OFFSET], nn, o;
6459 
6460 			// Node is TEXT_NODE or CDATA
6461 			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6462 				if (!startOffset) {
6463 					// At the start of text
6464 					startContainer.parentNode.insertBefore(n, startContainer);
6465 				} else if (startOffset >= startContainer.nodeValue.length) {
6466 					// At the end of text
6467 					dom.insertAfter(n, startContainer);
6468 				} else {
6469 					// Middle, need to split
6470 					nn = startContainer.splitText(startOffset);
6471 					startContainer.parentNode.insertBefore(n, nn);
6472 				}
6473 			} else {
6474 				// Insert element node
6475 				if (startContainer.childNodes.length > 0)
6476 					o = startContainer.childNodes[startOffset];
6477 
6478 				if (o)
6479 					startContainer.insertBefore(n, o);
6480 				else
6481 					startContainer.appendChild(n);
6482 			}
6483 		};
6484 
6485 		function surroundContents(n) {
6486 			var f = t.extractContents();
6487 
6488 			t.insertNode(n);
6489 			n.appendChild(f);
6490 			t.selectNode(n);
6491 		};
6492 
6493 		function cloneRange() {
6494 			return extend(new Range(dom), {
6495 				startContainer : t[START_CONTAINER],
6496 				startOffset : t[START_OFFSET],
6497 				endContainer : t[END_CONTAINER],
6498 				endOffset : t[END_OFFSET],
6499 				collapsed : t.collapsed,
6500 				commonAncestorContainer : t.commonAncestorContainer
6501 			});
6502 		};
6503 
6504 		// Private methods
6505 
6506 		function _getSelectedNode(container, offset) {
6507 			var child;
6508 
6509 			if (container.nodeType == 3 /* TEXT_NODE */)
6510 				return container;
6511 
6512 			if (offset < 0)
6513 				return container;
6514 
6515 			child = container.firstChild;
6516 			while (child && offset > 0) {
6517 				--offset;
6518 				child = child.nextSibling;
6519 			}
6520 
6521 			if (child)
6522 				return child;
6523 
6524 			return container;
6525 		};
6526 
6527 		function _isCollapsed() {
6528 			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6529 		};
6530 
6531 		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6532 			var c, offsetC, n, cmnRoot, childA, childB;
6533 			
6534 			// In the first case the boundary-points have the same container. A is before B
6535 			// if its offset is less than the offset of B, A is equal to B if its offset is
6536 			// equal to the offset of B, and A is after B if its offset is greater than the
6537 			// offset of B.
6538 			if (containerA == containerB) {
6539 				if (offsetA == offsetB)
6540 					return 0; // equal
6541 
6542 				if (offsetA < offsetB)
6543 					return -1; // before
6544 
6545 				return 1; // after
6546 			}
6547 
6548 			// In the second case a child node C of the container of A is an ancestor
6549 			// container of B. In this case, A is before B if the offset of A is less than or
6550 			// equal to the index of the child node C and A is after B otherwise.
6551 			c = containerB;
6552 			while (c && c.parentNode != containerA)
6553 				c = c.parentNode;
6554 
6555 			if (c) {
6556 				offsetC = 0;
6557 				n = containerA.firstChild;
6558 
6559 				while (n != c && offsetC < offsetA) {
6560 					offsetC++;
6561 					n = n.nextSibling;
6562 				}
6563 
6564 				if (offsetA <= offsetC)
6565 					return -1; // before
6566 
6567 				return 1; // after
6568 			}
6569 
6570 			// In the third case a child node C of the container of B is an ancestor container
6571 			// of A. In this case, A is before B if the index of the child node C is less than
6572 			// the offset of B and A is after B otherwise.
6573 			c = containerA;
6574 			while (c && c.parentNode != containerB) {
6575 				c = c.parentNode;
6576 			}
6577 
6578 			if (c) {
6579 				offsetC = 0;
6580 				n = containerB.firstChild;
6581 
6582 				while (n != c && offsetC < offsetB) {
6583 					offsetC++;
6584 					n = n.nextSibling;
6585 				}
6586 
6587 				if (offsetC < offsetB)
6588 					return -1; // before
6589 
6590 				return 1; // after
6591 			}
6592 
6593 			// In the fourth case, none of three other cases hold: the containers of A and B
6594 			// are siblings or descendants of sibling nodes. In this case, A is before B if
6595 			// the container of A is before the container of B in a pre-order traversal of the
6596 			// Ranges' context tree and A is after B otherwise.
6597 			cmnRoot = dom.findCommonAncestor(containerA, containerB);
6598 			childA = containerA;
6599 
6600 			while (childA && childA.parentNode != cmnRoot)
6601 				childA = childA.parentNode;
6602 
6603 			if (!childA)
6604 				childA = cmnRoot;
6605 
6606 			childB = containerB;
6607 			while (childB && childB.parentNode != cmnRoot)
6608 				childB = childB.parentNode;
6609 
6610 			if (!childB)
6611 				childB = cmnRoot;
6612 
6613 			if (childA == childB)
6614 				return 0; // equal
6615 
6616 			n = cmnRoot.firstChild;
6617 			while (n) {
6618 				if (n == childA)
6619 					return -1; // before
6620 
6621 				if (n == childB)
6622 					return 1; // after
6623 
6624 				n = n.nextSibling;
6625 			}
6626 		};
6627 
6628 		function _setEndPoint(st, n, o) {
6629 			var ec, sc;
6630 
6631 			if (st) {
6632 				t[START_CONTAINER] = n;
6633 				t[START_OFFSET] = o;
6634 			} else {
6635 				t[END_CONTAINER] = n;
6636 				t[END_OFFSET] = o;
6637 			}
6638 
6639 			// If one boundary-point of a Range is set to have a root container
6640 			// other than the current one for the Range, the Range is collapsed to
6641 			// the new position. This enforces the restriction that both boundary-
6642 			// points of a Range must have the same root container.
6643 			ec = t[END_CONTAINER];
6644 			while (ec.parentNode)
6645 				ec = ec.parentNode;
6646 
6647 			sc = t[START_CONTAINER];
6648 			while (sc.parentNode)
6649 				sc = sc.parentNode;
6650 
6651 			if (sc == ec) {
6652 				// The start position of a Range is guaranteed to never be after the
6653 				// end position. To enforce this restriction, if the start is set to
6654 				// be at a position after the end, the Range is collapsed to that
6655 				// position.
6656 				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6657 					t.collapse(st);
6658 			} else
6659 				t.collapse(st);
6660 
6661 			t.collapsed = _isCollapsed();
6662 			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6663 		};
6664 
6665 		function _traverse(how) {
6666 			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6667 
6668 			if (t[START_CONTAINER] == t[END_CONTAINER])
6669 				return _traverseSameContainer(how);
6670 
6671 			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6672 				if (p == t[START_CONTAINER])
6673 					return _traverseCommonStartContainer(c, how);
6674 
6675 				++endContainerDepth;
6676 			}
6677 
6678 			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6679 				if (p == t[END_CONTAINER])
6680 					return _traverseCommonEndContainer(c, how);
6681 
6682 				++startContainerDepth;
6683 			}
6684 
6685 			depthDiff = startContainerDepth - endContainerDepth;
6686 
6687 			startNode = t[START_CONTAINER];
6688 			while (depthDiff > 0) {
6689 				startNode = startNode.parentNode;
6690 				depthDiff--;
6691 			}
6692 
6693 			endNode = t[END_CONTAINER];
6694 			while (depthDiff < 0) {
6695 				endNode = endNode.parentNode;
6696 				depthDiff++;
6697 			}
6698 
6699 			// ascend the ancestor hierarchy until we have a common parent.
6700 			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6701 				startNode = sp;
6702 				endNode = ep;
6703 			}
6704 
6705 			return _traverseCommonAncestors(startNode, endNode, how);
6706 		};
6707 
6708 		 function _traverseSameContainer(how) {
6709 			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6710 
6711 			if (how != DELETE)
6712 				frag = createDocumentFragment();
6713 
6714 			// If selection is empty, just return the fragment
6715 			if (t[START_OFFSET] == t[END_OFFSET])
6716 				return frag;
6717 
6718 			// Text node needs special case handling
6719 			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6720 				// get the substring
6721 				s = t[START_CONTAINER].nodeValue;
6722 				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6723 
6724 				// set the original text node to its new value
6725 				if (how != CLONE) {
6726 					n = t[START_CONTAINER];
6727 					start = t[START_OFFSET];
6728 					len = t[END_OFFSET] - t[START_OFFSET];
6729 
6730 					if (start === 0 && len >= n.nodeValue.length - 1) {
6731 						n.parentNode.removeChild(n);
6732 					} else {
6733 						n.deleteData(start, len);
6734 					}
6735 
6736 					// Nothing is partially selected, so collapse to start point
6737 					t.collapse(TRUE);
6738 				}
6739 
6740 				if (how == DELETE)
6741 					return;
6742 
6743 				if (sub.length > 0) {
6744 					frag.appendChild(doc.createTextNode(sub));
6745 				}
6746 
6747 				return frag;
6748 			}
6749 
6750 			// Copy nodes between the start/end offsets.
6751 			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
6752 			cnt = t[END_OFFSET] - t[START_OFFSET];
6753 
6754 			while (n && cnt > 0) {
6755 				sibling = n.nextSibling;
6756 				xferNode = _traverseFullySelected(n, how);
6757 
6758 				if (frag)
6759 					frag.appendChild( xferNode );
6760 
6761 				--cnt;
6762 				n = sibling;
6763 			}
6764 
6765 			// Nothing is partially selected, so collapse to start point
6766 			if (how != CLONE)
6767 				t.collapse(TRUE);
6768 
6769 			return frag;
6770 		};
6771 
6772 		function _traverseCommonStartContainer(endAncestor, how) {
6773 			var frag, n, endIdx, cnt, sibling, xferNode;
6774 
6775 			if (how != DELETE)
6776 				frag = createDocumentFragment();
6777 
6778 			n = _traverseRightBoundary(endAncestor, how);
6779 
6780 			if (frag)
6781 				frag.appendChild(n);
6782 
6783 			endIdx = nodeIndex(endAncestor);
6784 			cnt = endIdx - t[START_OFFSET];
6785 
6786 			if (cnt <= 0) {
6787 				// Collapse to just before the endAncestor, which
6788 				// is partially selected.
6789 				if (how != CLONE) {
6790 					t.setEndBefore(endAncestor);
6791 					t.collapse(FALSE);
6792 				}
6793 
6794 				return frag;
6795 			}
6796 
6797 			n = endAncestor.previousSibling;
6798 			while (cnt > 0) {
6799 				sibling = n.previousSibling;
6800 				xferNode = _traverseFullySelected(n, how);
6801 
6802 				if (frag)
6803 					frag.insertBefore(xferNode, frag.firstChild);
6804 
6805 				--cnt;
6806 				n = sibling;
6807 			}
6808 
6809 			// Collapse to just before the endAncestor, which
6810 			// is partially selected.
6811 			if (how != CLONE) {
6812 				t.setEndBefore(endAncestor);
6813 				t.collapse(FALSE);
6814 			}
6815 
6816 			return frag;
6817 		};
6818 
6819 		function _traverseCommonEndContainer(startAncestor, how) {
6820 			var frag, startIdx, n, cnt, sibling, xferNode;
6821 
6822 			if (how != DELETE)
6823 				frag = createDocumentFragment();
6824 
6825 			n = _traverseLeftBoundary(startAncestor, how);
6826 			if (frag)
6827 				frag.appendChild(n);
6828 
6829 			startIdx = nodeIndex(startAncestor);
6830 			++startIdx; // Because we already traversed it
6831 
6832 			cnt = t[END_OFFSET] - startIdx;
6833 			n = startAncestor.nextSibling;
6834 			while (n && cnt > 0) {
6835 				sibling = n.nextSibling;
6836 				xferNode = _traverseFullySelected(n, how);
6837 
6838 				if (frag)
6839 					frag.appendChild(xferNode);
6840 
6841 				--cnt;
6842 				n = sibling;
6843 			}
6844 
6845 			if (how != CLONE) {
6846 				t.setStartAfter(startAncestor);
6847 				t.collapse(TRUE);
6848 			}
6849 
6850 			return frag;
6851 		};
6852 
6853 		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
6854 			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
6855 
6856 			if (how != DELETE)
6857 				frag = createDocumentFragment();
6858 
6859 			n = _traverseLeftBoundary(startAncestor, how);
6860 			if (frag)
6861 				frag.appendChild(n);
6862 
6863 			commonParent = startAncestor.parentNode;
6864 			startOffset = nodeIndex(startAncestor);
6865 			endOffset = nodeIndex(endAncestor);
6866 			++startOffset;
6867 
6868 			cnt = endOffset - startOffset;
6869 			sibling = startAncestor.nextSibling;
6870 
6871 			while (cnt > 0) {
6872 				nextSibling = sibling.nextSibling;
6873 				n = _traverseFullySelected(sibling, how);
6874 
6875 				if (frag)
6876 					frag.appendChild(n);
6877 
6878 				sibling = nextSibling;
6879 				--cnt;
6880 			}
6881 
6882 			n = _traverseRightBoundary(endAncestor, how);
6883 
6884 			if (frag)
6885 				frag.appendChild(n);
6886 
6887 			if (how != CLONE) {
6888 				t.setStartAfter(startAncestor);
6889 				t.collapse(TRUE);
6890 			}
6891 
6892 			return frag;
6893 		};
6894 
6895 		function _traverseRightBoundary(root, how) {
6896 			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
6897 
6898 			if (next == root)
6899 				return _traverseNode(next, isFullySelected, FALSE, how);
6900 
6901 			parent = next.parentNode;
6902 			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
6903 
6904 			while (parent) {
6905 				while (next) {
6906 					prevSibling = next.previousSibling;
6907 					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
6908 
6909 					if (how != DELETE)
6910 						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
6911 
6912 					isFullySelected = TRUE;
6913 					next = prevSibling;
6914 				}
6915 
6916 				if (parent == root)
6917 					return clonedParent;
6918 
6919 				next = parent.previousSibling;
6920 				parent = parent.parentNode;
6921 
6922 				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
6923 
6924 				if (how != DELETE)
6925 					clonedGrandParent.appendChild(clonedParent);
6926 
6927 				clonedParent = clonedGrandParent;
6928 			}
6929 		};
6930 
6931 		function _traverseLeftBoundary(root, how) {
6932 			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
6933 
6934 			if (next == root)
6935 				return _traverseNode(next, isFullySelected, TRUE, how);
6936 
6937 			parent = next.parentNode;
6938 			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
6939 
6940 			while (parent) {
6941 				while (next) {
6942 					nextSibling = next.nextSibling;
6943 					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
6944 
6945 					if (how != DELETE)
6946 						clonedParent.appendChild(clonedChild);
6947 
6948 					isFullySelected = TRUE;
6949 					next = nextSibling;
6950 				}
6951 
6952 				if (parent == root)
6953 					return clonedParent;
6954 
6955 				next = parent.nextSibling;
6956 				parent = parent.parentNode;
6957 
6958 				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
6959 
6960 				if (how != DELETE)
6961 					clonedGrandParent.appendChild(clonedParent);
6962 
6963 				clonedParent = clonedGrandParent;
6964 			}
6965 		};
6966 
6967 		function _traverseNode(n, isFullySelected, isLeft, how) {
6968 			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
6969 
6970 			if (isFullySelected)
6971 				return _traverseFullySelected(n, how);
6972 
6973 			if (n.nodeType == 3 /* TEXT_NODE */) {
6974 				txtValue = n.nodeValue;
6975 
6976 				if (isLeft) {
6977 					offset = t[START_OFFSET];
6978 					newNodeValue = txtValue.substring(offset);
6979 					oldNodeValue = txtValue.substring(0, offset);
6980 				} else {
6981 					offset = t[END_OFFSET];
6982 					newNodeValue = txtValue.substring(0, offset);
6983 					oldNodeValue = txtValue.substring(offset);
6984 				}
6985 
6986 				if (how != CLONE)
6987 					n.nodeValue = oldNodeValue;
6988 
6989 				if (how == DELETE)
6990 					return;
6991 
6992 				newNode = dom.clone(n, FALSE);
6993 				newNode.nodeValue = newNodeValue;
6994 
6995 				return newNode;
6996 			}
6997 
6998 			if (how == DELETE)
6999 				return;
7000 
7001 			return dom.clone(n, FALSE);
7002 		};
7003 
7004 		function _traverseFullySelected(n, how) {
7005 			if (how != DELETE)
7006 				return how == CLONE ? dom.clone(n, TRUE) : n;
7007 
7008 			n.parentNode.removeChild(n);
7009 		};
7010 
7011 		function toStringIE() {
7012 			return dom.create('body', null, cloneContents()).outerText;
7013 		}
7014 		
7015 		return t;
7016 	};
7017 
7018 	ns.Range = Range;
7019 
7020 	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7021 	Range.prototype.toString = function() {
7022 		return this.toStringIE();
7023 	};
7024 })(tinymce.dom);
7025 
7026 (function() {
7027 	function Selection(selection) {
7028 		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7029 
7030 		function getPosition(rng, start) {
7031 			var checkRng, startIndex = 0, endIndex, inside,
7032 				children, child, offset, index, position = -1, parent;
7033 
7034 			// Setup test range, collapse it and get the parent
7035 			checkRng = rng.duplicate();
7036 			checkRng.collapse(start);
7037 			parent = checkRng.parentElement();
7038 
7039 			// Check if the selection is within the right document
7040 			if (parent.ownerDocument !== selection.dom.doc)
7041 				return;
7042 
7043 			// IE will report non editable elements as it's parent so look for an editable one
7044 			while (parent.contentEditable === "false") {
7045 				parent = parent.parentNode;
7046 			}
7047 
7048 			// If parent doesn't have any children then return that we are inside the element
7049 			if (!parent.hasChildNodes()) {
7050 				return {node : parent, inside : 1};
7051 			}
7052 
7053 			// Setup node list and endIndex
7054 			children = parent.children;
7055 			endIndex = children.length - 1;
7056 
7057 			// Perform a binary search for the position
7058 			while (startIndex <= endIndex) {
7059 				index = Math.floor((startIndex + endIndex) / 2);
7060 
7061 				// Move selection to node and compare the ranges
7062 				child = children[index];
7063 				checkRng.moveToElementText(child);
7064 				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7065 
7066 				// Before/after or an exact match
7067 				if (position > 0) {
7068 					endIndex = index - 1;
7069 				} else if (position < 0) {
7070 					startIndex = index + 1;
7071 				} else {
7072 					return {node : child};
7073 				}
7074 			}
7075 
7076 			// Check if child position is before or we didn't find a position
7077 			if (position < 0) {
7078 				// No element child was found use the parent element and the offset inside that
7079 				if (!child) {
7080 					checkRng.moveToElementText(parent);
7081 					checkRng.collapse(true);
7082 					child = parent;
7083 					inside = true;
7084 				} else
7085 					checkRng.collapse(false);
7086 
7087 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7088 				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
7089 				offset = 0;
7090 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7091 					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7092 						break;
7093 					}
7094 
7095 					offset++;
7096 				}
7097 			} else {
7098 				// Child position is after the selection endpoint
7099 				checkRng.collapse(true);
7100 
7101 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7102 				offset = 0;
7103 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7104 					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7105 						break;
7106 					}
7107 
7108 					offset++;
7109 				}
7110 			}
7111 
7112 			return {node : child, position : position, offset : offset, inside : inside};
7113 		};
7114 
7115 		// Returns a W3C DOM compatible range object by using the IE Range API
7116 		function getRange() {
7117 			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7118 
7119 			// If selection is outside the current document just return an empty range
7120 			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7121 			if (element.ownerDocument != dom.doc)
7122 				return domRange;
7123 
7124 			collapsed = selection.isCollapsed();
7125 
7126 			// Handle control selection
7127 			if (ieRange.item) {
7128 				domRange.setStart(element.parentNode, dom.nodeIndex(element));
7129 				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7130 
7131 				return domRange;
7132 			}
7133 
7134 			function findEndPoint(start) {
7135 				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7136 
7137 				container = endPoint.node;
7138 				offset = endPoint.offset;
7139 
7140 				if (endPoint.inside && !container.hasChildNodes()) {
7141 					domRange[start ? 'setStart' : 'setEnd'](container, 0);
7142 					return;
7143 				}
7144 
7145 				if (offset === undef) {
7146 					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7147 					return;
7148 				}
7149 
7150 				if (endPoint.position < 0) {
7151 					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7152 
7153 					if (!sibling) {
7154 						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7155 						return;
7156 					}
7157 
7158 					if (!offset) {
7159 						if (sibling.nodeType == 3)
7160 							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7161 						else
7162 							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7163 
7164 						return;
7165 					}
7166 
7167 					// Find the text node and offset
7168 					while (sibling) {
7169 						nodeValue = sibling.nodeValue;
7170 						textNodeOffset += nodeValue.length;
7171 
7172 						// We are at or passed the position we where looking for
7173 						if (textNodeOffset >= offset) {
7174 							container = sibling;
7175 							textNodeOffset -= offset;
7176 							textNodeOffset = nodeValue.length - textNodeOffset;
7177 							break;
7178 						}
7179 
7180 						sibling = sibling.nextSibling;
7181 					}
7182 				} else {
7183 					// Find the text node and offset
7184 					sibling = container.previousSibling;
7185 
7186 					if (!sibling)
7187 						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7188 
7189 					// If there isn't any text to loop then use the first position
7190 					if (!offset) {
7191 						if (container.nodeType == 3)
7192 							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7193 						else
7194 							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7195 
7196 						return;
7197 					}
7198 
7199 					while (sibling) {
7200 						textNodeOffset += sibling.nodeValue.length;
7201 
7202 						// We are at or passed the position we where looking for
7203 						if (textNodeOffset >= offset) {
7204 							container = sibling;
7205 							textNodeOffset -= offset;
7206 							break;
7207 						}
7208 
7209 						sibling = sibling.previousSibling;
7210 					}
7211 				}
7212 
7213 				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7214 			};
7215 
7216 			try {
7217 				// Find start point
7218 				findEndPoint(true);
7219 
7220 				// Find end point if needed
7221 				if (!collapsed)
7222 					findEndPoint();
7223 			} catch (ex) {
7224 				// IE has a nasty bug where text nodes might throw "invalid argument" when you
7225 				// access the nodeValue or other properties of text nodes. This seems to happend when
7226 				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7227 				if (ex.number == -2147024809) {
7228 					// Get the current selection
7229 					bookmark = self.getBookmark(2);
7230 
7231 					// Get start element
7232 					tmpRange = ieRange.duplicate();
7233 					tmpRange.collapse(true);
7234 					element = tmpRange.parentElement();
7235 
7236 					// Get end element
7237 					if (!collapsed) {
7238 						tmpRange = ieRange.duplicate();
7239 						tmpRange.collapse(false);
7240 						element2 = tmpRange.parentElement();
7241 						element2.innerHTML = element2.innerHTML;
7242 					}
7243 
7244 					// Remove the broken elements
7245 					element.innerHTML = element.innerHTML;
7246 
7247 					// Restore the selection
7248 					self.moveToBookmark(bookmark);
7249 
7250 					// Since the range has moved we need to re-get it
7251 					ieRange = selection.getRng();
7252 
7253 					// Find start point
7254 					findEndPoint(true);
7255 
7256 					// Find end point if needed
7257 					if (!collapsed)
7258 						findEndPoint();
7259 				} else
7260 					throw ex; // Throw other errors
7261 			}
7262 
7263 			return domRange;
7264 		};
7265 
7266 		this.getBookmark = function(type) {
7267 			var rng = selection.getRng(), start, end, bookmark = {};
7268 
7269 			function getIndexes(node) {
7270 				var parent, root, children, i, indexes = [];
7271 
7272 				parent = node.parentNode;
7273 				root = dom.getRoot().parentNode;
7274 
7275 				while (parent != root && parent.nodeType !== 9) {
7276 					children = parent.children;
7277 
7278 					i = children.length;
7279 					while (i--) {
7280 						if (node === children[i]) {
7281 							indexes.push(i);
7282 							break;
7283 						}
7284 					}
7285 
7286 					node = parent;
7287 					parent = parent.parentNode;
7288 				}
7289 
7290 				return indexes;
7291 			};
7292 
7293 			function getBookmarkEndPoint(start) {
7294 				var position;
7295 
7296 				position = getPosition(rng, start);
7297 				if (position) {
7298 					return {
7299 						position : position.position,
7300 						offset : position.offset,
7301 						indexes : getIndexes(position.node),
7302 						inside : position.inside
7303 					};
7304 				}
7305 			};
7306 
7307 			// Non ubstructive bookmark
7308 			if (type === 2) {
7309 				// Handle text selection
7310 				if (!rng.item) {
7311 					bookmark.start = getBookmarkEndPoint(true);
7312 
7313 					if (!selection.isCollapsed())
7314 						bookmark.end = getBookmarkEndPoint();
7315 				} else
7316 					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7317 			}
7318 
7319 			return bookmark;
7320 		};
7321 
7322 		this.moveToBookmark = function(bookmark) {
7323 			var rng, body = dom.doc.body;
7324 
7325 			function resolveIndexes(indexes) {
7326 				var node, i, idx, children;
7327 
7328 				node = dom.getRoot();
7329 				for (i = indexes.length - 1; i >= 0; i--) {
7330 					children = node.children;
7331 					idx = indexes[i];
7332 
7333 					if (idx <= children.length - 1) {
7334 						node = children[idx];
7335 					}
7336 				}
7337 
7338 				return node;
7339 			};
7340 			
7341 			function setBookmarkEndPoint(start) {
7342 				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7343 
7344 				if (endPoint) {
7345 					moveLeft = endPoint.position > 0;
7346 
7347 					moveRng = body.createTextRange();
7348 					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7349 
7350 					offset = endPoint.offset;
7351 					if (offset !== undef) {
7352 						moveRng.collapse(endPoint.inside || moveLeft);
7353 						moveRng.moveStart('character', moveLeft ? -offset : offset);
7354 					} else
7355 						moveRng.collapse(start);
7356 
7357 					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7358 
7359 					if (start)
7360 						rng.collapse(true);
7361 				}
7362 			};
7363 
7364 			if (bookmark.start) {
7365 				if (bookmark.start.ctrl) {
7366 					rng = body.createControlRange();
7367 					rng.addElement(resolveIndexes(bookmark.start.indexes));
7368 					rng.select();
7369 				} else {
7370 					rng = body.createTextRange();
7371 					setBookmarkEndPoint(true);
7372 					setBookmarkEndPoint();
7373 					rng.select();
7374 				}
7375 			}
7376 		};
7377 
7378 		this.addRange = function(rng) {
7379 			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body;
7380 
7381 			function setEndPoint(start) {
7382 				var container, offset, marker, tmpRng, nodes;
7383 
7384 				marker = dom.create('a');
7385 				container = start ? startContainer : endContainer;
7386 				offset = start ? startOffset : endOffset;
7387 				tmpRng = ieRng.duplicate();
7388 
7389 				if (container == doc || container == doc.documentElement) {
7390 					container = body;
7391 					offset = 0;
7392 				}
7393 
7394 				if (container.nodeType == 3) {
7395 					container.parentNode.insertBefore(marker, container);
7396 					tmpRng.moveToElementText(marker);
7397 					tmpRng.moveStart('character', offset);
7398 					dom.remove(marker);
7399 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7400 				} else {
7401 					nodes = container.childNodes;
7402 
7403 					if (nodes.length) {
7404 						if (offset >= nodes.length) {
7405 							dom.insertAfter(marker, nodes[nodes.length - 1]);
7406 						} else {
7407 							container.insertBefore(marker, nodes[offset]);
7408 						}
7409 
7410 						tmpRng.moveToElementText(marker);
7411 					} else if (container.canHaveHTML) {
7412 						// Empty node selection for example <div>|</div>
7413 						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7414 						container.innerHTML = '<span>\uFEFF</span>';
7415 						marker = container.firstChild;
7416 						tmpRng.moveToElementText(marker);
7417 						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7418 					}
7419 
7420 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7421 					dom.remove(marker);
7422 				}
7423 			}
7424 
7425 			// Setup some shorter versions
7426 			startContainer = rng.startContainer;
7427 			startOffset = rng.startOffset;
7428 			endContainer = rng.endContainer;
7429 			endOffset = rng.endOffset;
7430 			ieRng = body.createTextRange();
7431 
7432 			// If single element selection then try making a control selection out of it
7433 			if (startContainer == endContainer && startContainer.nodeType == 1) {
7434 				// Trick to place the caret inside an empty block element like <p></p>
7435 				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7436 					if (startContainer.canHaveHTML) {
7437 						// Check if previous sibling is an empty block if it is then we need to render it
7438 						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7439 						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7440 						sibling = startContainer.previousSibling;
7441 						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7442 							sibling.innerHTML = '\uFEFF';
7443 						} else {
7444 							sibling = null;
7445 						}
7446 
7447 						startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7448 						ieRng.moveToElementText(startContainer.lastChild);
7449 						ieRng.select();
7450 						dom.doc.selection.clear();
7451 						startContainer.innerHTML = '';
7452 
7453 						if (sibling) {
7454 							sibling.innerHTML = '';
7455 						}
7456 						return;
7457 					} else {
7458 						startOffset = dom.nodeIndex(startContainer);
7459 						startContainer = startContainer.parentNode;
7460 					}
7461 				}
7462 
7463 				if (startOffset == endOffset - 1) {
7464 					try {
7465 						ctrlRng = body.createControlRange();
7466 						ctrlRng.addElement(startContainer.childNodes[startOffset]);
7467 						ctrlRng.select();
7468 						return;
7469 					} catch (ex) {
7470 						// Ignore
7471 					}
7472 				}
7473 			}
7474 
7475 			// Set start/end point of selection
7476 			setEndPoint(true);
7477 			setEndPoint();
7478 
7479 			// Select the new range and scroll it into view
7480 			ieRng.select();
7481 		};
7482 
7483 		// Expose range method
7484 		this.getRangeAt = getRange;
7485 	};
7486 
7487 	// Expose the selection object
7488 	tinymce.dom.TridentSelection = Selection;
7489 })();
7490 
7491 
7492 /*
7493  * Sizzle CSS Selector Engine
7494  *  Copyright, The Dojo Foundation
7495  *  Released under the MIT, BSD, and GPL Licenses.
7496  *  More information: http://sizzlejs.com/
7497  */
7498 (function(){
7499 
7500 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
7501 	expando = "sizcache",
7502 	done = 0,
7503 	toString = Object.prototype.toString,
7504 	hasDuplicate = false,
7505 	baseHasDuplicate = true,
7506 	rBackslash = /\\/g,
7507 	rReturn = /\r\n/g,
7508 	rNonWord = /\W/;
7509 
7510 // Here we check if the JavaScript engine is using some sort of
7511 // optimization where it does not always call our comparision
7512 // function. If that is the case, discard the hasDuplicate value.
7513 //   Thus far that includes Google Chrome.
7514 [0, 0].sort(function() {
7515 	baseHasDuplicate = false;
7516 	return 0;
7517 });
7518 
7519 var Sizzle = function( selector, context, results, seed ) {
7520 	results = results || [];
7521 	context = context || document;
7522 
7523 	var origContext = context;
7524 
7525 	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
7526 		return [];
7527 	}
7528 
7529 	if ( !selector || typeof selector !== "string" ) {
7530 		return results;
7531 	}
7532 
7533 	var m, set, checkSet, extra, ret, cur, pop, i,
7534 		prune = true,
7535 		contextXML = Sizzle.isXML( context ),
7536 		parts = [],
7537 		soFar = selector;
7538 
7539 	// Reset the position of the chunker regexp (start from head)
7540 	do {
7541 		chunker.exec( "" );
7542 		m = chunker.exec( soFar );
7543 
7544 		if ( m ) {
7545 			soFar = m[3];
7546 
7547 			parts.push( m[1] );
7548 
7549 			if ( m[2] ) {
7550 				extra = m[3];
7551 				break;
7552 			}
7553 		}
7554 	} while ( m );
7555 
7556 	if ( parts.length > 1 && origPOS.exec( selector ) ) {
7557 
7558 		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
7559 			set = posProcess( parts[0] + parts[1], context, seed );
7560 
7561 		} else {
7562 			set = Expr.relative[ parts[0] ] ?
7563 				[ context ] :
7564 				Sizzle( parts.shift(), context );
7565 
7566 			while ( parts.length ) {
7567 				selector = parts.shift();
7568 
7569 				if ( Expr.relative[ selector ] ) {
7570 					selector += parts.shift();
7571 				}
7572 
7573 				set = posProcess( selector, set, seed );
7574 			}
7575 		}
7576 
7577 	} else {
7578 		// Take a shortcut and set the context if the root selector is an ID
7579 		// (but not if it'll be faster if the inner selector is an ID)
7580 		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
7581 				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
7582 
7583 			ret = Sizzle.find( parts.shift(), context, contextXML );
7584 			context = ret.expr ?
7585 				Sizzle.filter( ret.expr, ret.set )[0] :
7586 				ret.set[0];
7587 		}
7588 
7589 		if ( context ) {
7590 			ret = seed ?
7591 				{ expr: parts.pop(), set: makeArray(seed) } :
7592 				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
7593 
7594 			set = ret.expr ?
7595 				Sizzle.filter( ret.expr, ret.set ) :
7596 				ret.set;
7597 
7598 			if ( parts.length > 0 ) {
7599 				checkSet = makeArray( set );
7600 
7601 			} else {
7602 				prune = false;
7603 			}
7604 
7605 			while ( parts.length ) {
7606 				cur = parts.pop();
7607 				pop = cur;
7608 
7609 				if ( !Expr.relative[ cur ] ) {
7610 					cur = "";
7611 				} else {
7612 					pop = parts.pop();
7613 				}
7614 
7615 				if ( pop == null ) {
7616 					pop = context;
7617 				}
7618 
7619 				Expr.relative[ cur ]( checkSet, pop, contextXML );
7620 			}
7621 
7622 		} else {
7623 			checkSet = parts = [];
7624 		}
7625 	}
7626 
7627 	if ( !checkSet ) {
7628 		checkSet = set;
7629 	}
7630 
7631 	if ( !checkSet ) {
7632 		Sizzle.error( cur || selector );
7633 	}
7634 
7635 	if ( toString.call(checkSet) === "[object Array]" ) {
7636 		if ( !prune ) {
7637 			results.push.apply( results, checkSet );
7638 
7639 		} else if ( context && context.nodeType === 1 ) {
7640 			for ( i = 0; checkSet[i] != null; i++ ) {
7641 				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
7642 					results.push( set[i] );
7643 				}
7644 			}
7645 
7646 		} else {
7647 			for ( i = 0; checkSet[i] != null; i++ ) {
7648 				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
7649 					results.push( set[i] );
7650 				}
7651 			}
7652 		}
7653 
7654 	} else {
7655 		makeArray( checkSet, results );
7656 	}
7657 
7658 	if ( extra ) {
7659 		Sizzle( extra, origContext, results, seed );
7660 		Sizzle.uniqueSort( results );
7661 	}
7662 
7663 	return results;
7664 };
7665 
7666 Sizzle.uniqueSort = function( results ) {
7667 	if ( sortOrder ) {
7668 		hasDuplicate = baseHasDuplicate;
7669 		results.sort( sortOrder );
7670 
7671 		if ( hasDuplicate ) {
7672 			for ( var i = 1; i < results.length; i++ ) {
7673 				if ( results[i] === results[ i - 1 ] ) {
7674 					results.splice( i--, 1 );
7675 				}
7676 			}
7677 		}
7678 	}
7679 
7680 	return results;
7681 };
7682 
7683 Sizzle.matches = function( expr, set ) {
7684 	return Sizzle( expr, null, null, set );
7685 };
7686 
7687 Sizzle.matchesSelector = function( node, expr ) {
7688 	return Sizzle( expr, null, null, [node] ).length > 0;
7689 };
7690 
7691 Sizzle.find = function( expr, context, isXML ) {
7692 	var set, i, len, match, type, left;
7693 
7694 	if ( !expr ) {
7695 		return [];
7696 	}
7697 
7698 	for ( i = 0, len = Expr.order.length; i < len; i++ ) {
7699 		type = Expr.order[i];
7700 
7701 		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
7702 			left = match[1];
7703 			match.splice( 1, 1 );
7704 
7705 			if ( left.substr( left.length - 1 ) !== "\\" ) {
7706 				match[1] = (match[1] || "").replace( rBackslash, "" );
7707 				set = Expr.find[ type ]( match, context, isXML );
7708 
7709 				if ( set != null ) {
7710 					expr = expr.replace( Expr.match[ type ], "" );
7711 					break;
7712 				}
7713 			}
7714 		}
7715 	}
7716 
7717 	if ( !set ) {
7718 		set = typeof context.getElementsByTagName !== "undefined" ?
7719 			context.getElementsByTagName( "*" ) :
7720 			[];
7721 	}
7722 
7723 	return { set: set, expr: expr };
7724 };
7725 
7726 Sizzle.filter = function( expr, set, inplace, not ) {
7727 	var match, anyFound,
7728 		type, found, item, filter, left,
7729 		i, pass,
7730 		old = expr,
7731 		result = [],
7732 		curLoop = set,
7733 		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
7734 
7735 	while ( expr && set.length ) {
7736 		for ( type in Expr.filter ) {
7737 			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
7738 				filter = Expr.filter[ type ];
7739 				left = match[1];
7740 
7741 				anyFound = false;
7742 
7743 				match.splice(1,1);
7744 
7745 				if ( left.substr( left.length - 1 ) === "\\" ) {
7746 					continue;
7747 				}
7748 
7749 				if ( curLoop === result ) {
7750 					result = [];
7751 				}
7752 
7753 				if ( Expr.preFilter[ type ] ) {
7754 					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
7755 
7756 					if ( !match ) {
7757 						anyFound = found = true;
7758 
7759 					} else if ( match === true ) {
7760 						continue;
7761 					}
7762 				}
7763 
7764 				if ( match ) {
7765 					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
7766 						if ( item ) {
7767 							found = filter( item, match, i, curLoop );
7768 							pass = not ^ found;
7769 
7770 							if ( inplace && found != null ) {
7771 								if ( pass ) {
7772 									anyFound = true;
7773 
7774 								} else {
7775 									curLoop[i] = false;
7776 								}
7777 
7778 							} else if ( pass ) {
7779 								result.push( item );
7780 								anyFound = true;
7781 							}
7782 						}
7783 					}
7784 				}
7785 
7786 				if ( found !== undefined ) {
7787 					if ( !inplace ) {
7788 						curLoop = result;
7789 					}
7790 
7791 					expr = expr.replace( Expr.match[ type ], "" );
7792 
7793 					if ( !anyFound ) {
7794 						return [];
7795 					}
7796 
7797 					break;
7798 				}
7799 			}
7800 		}
7801 
7802 		// Improper expression
7803 		if ( expr === old ) {
7804 			if ( anyFound == null ) {
7805 				Sizzle.error( expr );
7806 
7807 			} else {
7808 				break;
7809 			}
7810 		}
7811 
7812 		old = expr;
7813 	}
7814 
7815 	return curLoop;
7816 };
7817 
7818 Sizzle.error = function( msg ) {
7819 	throw new Error( "Syntax error, unrecognized expression: " + msg );
7820 };
7821 
7822 var getText = Sizzle.getText = function( elem ) {
7823     var i, node,
7824 		nodeType = elem.nodeType,
7825 		ret = "";
7826 
7827 	if ( nodeType ) {
7828 		if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
7829 			// Use textContent || innerText for elements
7830 			if ( typeof elem.textContent === 'string' ) {
7831 				return elem.textContent;
7832 			} else if ( typeof elem.innerText === 'string' ) {
7833 				// Replace IE's carriage returns
7834 				return elem.innerText.replace( rReturn, '' );
7835 			} else {
7836 				// Traverse it's children
7837 				for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
7838 					ret += getText( elem );
7839 				}
7840 			}
7841 		} else if ( nodeType === 3 || nodeType === 4 ) {
7842 			return elem.nodeValue;
7843 		}
7844 	} else {
7845 
7846 		// If no nodeType, this is expected to be an array
7847 		for ( i = 0; (node = elem[i]); i++ ) {
7848 			// Do not traverse comment nodes
7849 			if ( node.nodeType !== 8 ) {
7850 				ret += getText( node );
7851 			}
7852 		}
7853 	}
7854 	return ret;
7855 };
7856 
7857 var Expr = Sizzle.selectors = {
7858 	order: [ "ID", "NAME", "TAG" ],
7859 
7860 	match: {
7861 		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7862 		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7863 		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
7864 		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
7865 		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
7866 		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
7867 		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
7868 		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
7869 	},
7870 
7871 	leftMatch: {},
7872 
7873 	attrMap: {
7874 		"class": "className",
7875 		"for": "htmlFor"
7876 	},
7877 
7878 	attrHandle: {
7879 		href: function( elem ) {
7880 			return elem.getAttribute( "href" );
7881 		},
7882 		type: function( elem ) {
7883 			return elem.getAttribute( "type" );
7884 		}
7885 	},
7886 
7887 	relative: {
7888 		"+": function(checkSet, part){
7889 			var isPartStr = typeof part === "string",
7890 				isTag = isPartStr && !rNonWord.test( part ),
7891 				isPartStrNotTag = isPartStr && !isTag;
7892 
7893 			if ( isTag ) {
7894 				part = part.toLowerCase();
7895 			}
7896 
7897 			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
7898 				if ( (elem = checkSet[i]) ) {
7899 					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
7900 
7901 					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
7902 						elem || false :
7903 						elem === part;
7904 				}
7905 			}
7906 
7907 			if ( isPartStrNotTag ) {
7908 				Sizzle.filter( part, checkSet, true );
7909 			}
7910 		},
7911 
7912 		">": function( checkSet, part ) {
7913 			var elem,
7914 				isPartStr = typeof part === "string",
7915 				i = 0,
7916 				l = checkSet.length;
7917 
7918 			if ( isPartStr && !rNonWord.test( part ) ) {
7919 				part = part.toLowerCase();
7920 
7921 				for ( ; i < l; i++ ) {
7922 					elem = checkSet[i];
7923 
7924 					if ( elem ) {
7925 						var parent = elem.parentNode;
7926 						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
7927 					}
7928 				}
7929 
7930 			} else {
7931 				for ( ; i < l; i++ ) {
7932 					elem = checkSet[i];
7933 
7934 					if ( elem ) {
7935 						checkSet[i] = isPartStr ?
7936 							elem.parentNode :
7937 							elem.parentNode === part;
7938 					}
7939 				}
7940 
7941 				if ( isPartStr ) {
7942 					Sizzle.filter( part, checkSet, true );
7943 				}
7944 			}
7945 		},
7946 
7947 		"": function(checkSet, part, isXML){
7948 			var nodeCheck,
7949 				doneName = done++,
7950 				checkFn = dirCheck;
7951 
7952 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7953 				part = part.toLowerCase();
7954 				nodeCheck = part;
7955 				checkFn = dirNodeCheck;
7956 			}
7957 
7958 			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
7959 		},
7960 
7961 		"~": function( checkSet, part, isXML ) {
7962 			var nodeCheck,
7963 				doneName = done++,
7964 				checkFn = dirCheck;
7965 
7966 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7967 				part = part.toLowerCase();
7968 				nodeCheck = part;
7969 				checkFn = dirNodeCheck;
7970 			}
7971 
7972 			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
7973 		}
7974 	},
7975 
7976 	find: {
7977 		ID: function( match, context, isXML ) {
7978 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
7979 				var m = context.getElementById(match[1]);
7980 				// Check parentNode to catch when Blackberry 4.6 returns
7981 				// nodes that are no longer in the document #6963
7982 				return m && m.parentNode ? [m] : [];
7983 			}
7984 		},
7985 
7986 		NAME: function( match, context ) {
7987 			if ( typeof context.getElementsByName !== "undefined" ) {
7988 				var ret = [],
7989 					results = context.getElementsByName( match[1] );
7990 
7991 				for ( var i = 0, l = results.length; i < l; i++ ) {
7992 					if ( results[i].getAttribute("name") === match[1] ) {
7993 						ret.push( results[i] );
7994 					}
7995 				}
7996 
7997 				return ret.length === 0 ? null : ret;
7998 			}
7999 		},
8000 
8001 		TAG: function( match, context ) {
8002 			if ( typeof context.getElementsByTagName !== "undefined" ) {
8003 				return context.getElementsByTagName( match[1] );
8004 			}
8005 		}
8006 	},
8007 	preFilter: {
8008 		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
8009 			match = " " + match[1].replace( rBackslash, "" ) + " ";
8010 
8011 			if ( isXML ) {
8012 				return match;
8013 			}
8014 
8015 			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
8016 				if ( elem ) {
8017 					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
8018 						if ( !inplace ) {
8019 							result.push( elem );
8020 						}
8021 
8022 					} else if ( inplace ) {
8023 						curLoop[i] = false;
8024 					}
8025 				}
8026 			}
8027 
8028 			return false;
8029 		},
8030 
8031 		ID: function( match ) {
8032 			return match[1].replace( rBackslash, "" );
8033 		},
8034 
8035 		TAG: function( match, curLoop ) {
8036 			return match[1].replace( rBackslash, "" ).toLowerCase();
8037 		},
8038 
8039 		CHILD: function( match ) {
8040 			if ( match[1] === "nth" ) {
8041 				if ( !match[2] ) {
8042 					Sizzle.error( match[0] );
8043 				}
8044 
8045 				match[2] = match[2].replace(/^\+|\s*/g, '');
8046 
8047 				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
8048 				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
8049 					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
8050 					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
8051 
8052 				// calculate the numbers (first)n+(last) including if they are negative
8053 				match[2] = (test[1] + (test[2] || 1)) - 0;
8054 				match[3] = test[3] - 0;
8055 			}
8056 			else if ( match[2] ) {
8057 				Sizzle.error( match[0] );
8058 			}
8059 
8060 			// TODO: Move to normal caching system
8061 			match[0] = done++;
8062 
8063 			return match;
8064 		},
8065 
8066 		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
8067 			var name = match[1] = match[1].replace( rBackslash, "" );
8068 
8069 			if ( !isXML && Expr.attrMap[name] ) {
8070 				match[1] = Expr.attrMap[name];
8071 			}
8072 
8073 			// Handle if an un-quoted value was used
8074 			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
8075 
8076 			if ( match[2] === "~=" ) {
8077 				match[4] = " " + match[4] + " ";
8078 			}
8079 
8080 			return match;
8081 		},
8082 
8083 		PSEUDO: function( match, curLoop, inplace, result, not ) {
8084 			if ( match[1] === "not" ) {
8085 				// If we're dealing with a complex expression, or a simple one
8086 				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
8087 					match[3] = Sizzle(match[3], null, null, curLoop);
8088 
8089 				} else {
8090 					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
8091 
8092 					if ( !inplace ) {
8093 						result.push.apply( result, ret );
8094 					}
8095 
8096 					return false;
8097 				}
8098 
8099 			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
8100 				return true;
8101 			}
8102 
8103 			return match;
8104 		},
8105 
8106 		POS: function( match ) {
8107 			match.unshift( true );
8108 
8109 			return match;
8110 		}
8111 	},
8112 
8113 	filters: {
8114 		enabled: function( elem ) {
8115 			return elem.disabled === false && elem.type !== "hidden";
8116 		},
8117 
8118 		disabled: function( elem ) {
8119 			return elem.disabled === true;
8120 		},
8121 
8122 		checked: function( elem ) {
8123 			return elem.checked === true;
8124 		},
8125 
8126 		selected: function( elem ) {
8127 			// Accessing this property makes selected-by-default
8128 			// options in Safari work properly
8129 			if ( elem.parentNode ) {
8130 				elem.parentNode.selectedIndex;
8131 			}
8132 
8133 			return elem.selected === true;
8134 		},
8135 
8136 		parent: function( elem ) {
8137 			return !!elem.firstChild;
8138 		},
8139 
8140 		empty: function( elem ) {
8141 			return !elem.firstChild;
8142 		},
8143 
8144 		has: function( elem, i, match ) {
8145 			return !!Sizzle( match[3], elem ).length;
8146 		},
8147 
8148 		header: function( elem ) {
8149 			return (/h\d/i).test( elem.nodeName );
8150 		},
8151 
8152 		text: function( elem ) {
8153 			var attr = elem.getAttribute( "type" ), type = elem.type;
8154 			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
8155 			// use getAttribute instead to test this case
8156 			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
8157 		},
8158 
8159 		radio: function( elem ) {
8160 			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
8161 		},
8162 
8163 		checkbox: function( elem ) {
8164 			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
8165 		},
8166 
8167 		file: function( elem ) {
8168 			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
8169 		},
8170 
8171 		password: function( elem ) {
8172 			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
8173 		},
8174 
8175 		submit: function( elem ) {
8176 			var name = elem.nodeName.toLowerCase();
8177 			return (name === "input" || name === "button") && "submit" === elem.type;
8178 		},
8179 
8180 		image: function( elem ) {
8181 			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
8182 		},
8183 
8184 		reset: function( elem ) {
8185 			var name = elem.nodeName.toLowerCase();
8186 			return (name === "input" || name === "button") && "reset" === elem.type;
8187 		},
8188 
8189 		button: function( elem ) {
8190 			var name = elem.nodeName.toLowerCase();
8191 			return name === "input" && "button" === elem.type || name === "button";
8192 		},
8193 
8194 		input: function( elem ) {
8195 			return (/input|select|textarea|button/i).test( elem.nodeName );
8196 		},
8197 
8198 		focus: function( elem ) {
8199 			return elem === elem.ownerDocument.activeElement;
8200 		}
8201 	},
8202 	setFilters: {
8203 		first: function( elem, i ) {
8204 			return i === 0;
8205 		},
8206 
8207 		last: function( elem, i, match, array ) {
8208 			return i === array.length - 1;
8209 		},
8210 
8211 		even: function( elem, i ) {
8212 			return i % 2 === 0;
8213 		},
8214 
8215 		odd: function( elem, i ) {
8216 			return i % 2 === 1;
8217 		},
8218 
8219 		lt: function( elem, i, match ) {
8220 			return i < match[3] - 0;
8221 		},
8222 
8223 		gt: function( elem, i, match ) {
8224 			return i > match[3] - 0;
8225 		},
8226 
8227 		nth: function( elem, i, match ) {
8228 			return match[3] - 0 === i;
8229 		},
8230 
8231 		eq: function( elem, i, match ) {
8232 			return match[3] - 0 === i;
8233 		}
8234 	},
8235 	filter: {
8236 		PSEUDO: function( elem, match, i, array ) {
8237 			var name = match[1],
8238 				filter = Expr.filters[ name ];
8239 
8240 			if ( filter ) {
8241 				return filter( elem, i, match, array );
8242 
8243 			} else if ( name === "contains" ) {
8244 				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
8245 
8246 			} else if ( name === "not" ) {
8247 				var not = match[3];
8248 
8249 				for ( var j = 0, l = not.length; j < l; j++ ) {
8250 					if ( not[j] === elem ) {
8251 						return false;
8252 					}
8253 				}
8254 
8255 				return true;
8256 
8257 			} else {
8258 				Sizzle.error( name );
8259 			}
8260 		},
8261 
8262 		CHILD: function( elem, match ) {
8263 			var first, last,
8264 				doneName, parent, cache,
8265 				count, diff,
8266 				type = match[1],
8267 				node = elem;
8268 
8269 			switch ( type ) {
8270 				case "only":
8271 				case "first":
8272 					while ( (node = node.previousSibling) ) {
8273 						if ( node.nodeType === 1 ) {
8274 							return false;
8275 						}
8276 					}
8277 
8278 					if ( type === "first" ) {
8279 						return true;
8280 					}
8281 
8282 					node = elem;
8283 
8284 					/* falls through */
8285 				case "last":
8286 					while ( (node = node.nextSibling) ) {
8287 						if ( node.nodeType === 1 ) {
8288 							return false;
8289 						}
8290 					}
8291 
8292 					return true;
8293 
8294 				case "nth":
8295 					first = match[2];
8296 					last = match[3];
8297 
8298 					if ( first === 1 && last === 0 ) {
8299 						return true;
8300 					}
8301 
8302 					doneName = match[0];
8303 					parent = elem.parentNode;
8304 
8305 					if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
8306 						count = 0;
8307 
8308 						for ( node = parent.firstChild; node; node = node.nextSibling ) {
8309 							if ( node.nodeType === 1 ) {
8310 								node.nodeIndex = ++count;
8311 							}
8312 						}
8313 
8314 						parent[ expando ] = doneName;
8315 					}
8316 
8317 					diff = elem.nodeIndex - last;
8318 
8319 					if ( first === 0 ) {
8320 						return diff === 0;
8321 
8322 					} else {
8323 						return ( diff % first === 0 && diff / first >= 0 );
8324 					}
8325 			}
8326 		},
8327 
8328 		ID: function( elem, match ) {
8329 			return elem.nodeType === 1 && elem.getAttribute("id") === match;
8330 		},
8331 
8332 		TAG: function( elem, match ) {
8333 			return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
8334 		},
8335 
8336 		CLASS: function( elem, match ) {
8337 			return (" " + (elem.className || elem.getAttribute("class")) + " ")
8338 				.indexOf( match ) > -1;
8339 		},
8340 
8341 		ATTR: function( elem, match ) {
8342 			var name = match[1],
8343 				result = Sizzle.attr ?
8344 					Sizzle.attr( elem, name ) :
8345 					Expr.attrHandle[ name ] ?
8346 					Expr.attrHandle[ name ]( elem ) :
8347 					elem[ name ] != null ?
8348 						elem[ name ] :
8349 						elem.getAttribute( name ),
8350 				value = result + "",
8351 				type = match[2],
8352 				check = match[4];
8353 
8354 			return result == null ?
8355 				type === "!=" :
8356 				!type && Sizzle.attr ?
8357 				result != null :
8358 				type === "=" ?
8359 				value === check :
8360 				type === "*=" ?
8361 				value.indexOf(check) >= 0 :
8362 				type === "~=" ?
8363 				(" " + value + " ").indexOf(check) >= 0 :
8364 				!check ?
8365 				value && result !== false :
8366 				type === "!=" ?
8367 				value !== check :
8368 				type === "^=" ?
8369 				value.indexOf(check) === 0 :
8370 				type === "$=" ?
8371 				value.substr(value.length - check.length) === check :
8372 				type === "|=" ?
8373 				value === check || value.substr(0, check.length + 1) === check + "-" :
8374 				false;
8375 		},
8376 
8377 		POS: function( elem, match, i, array ) {
8378 			var name = match[2],
8379 				filter = Expr.setFilters[ name ];
8380 
8381 			if ( filter ) {
8382 				return filter( elem, i, match, array );
8383 			}
8384 		}
8385 	}
8386 };
8387 
8388 var origPOS = Expr.match.POS,
8389 	fescape = function(all, num){
8390 		return "\\" + (num - 0 + 1);
8391 	};
8392 
8393 for ( var type in Expr.match ) {
8394 	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
8395 	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
8396 }
8397 // Expose origPOS
8398 // "global" as in regardless of relation to brackets/parens
8399 Expr.match.globalPOS = origPOS;
8400 
8401 var makeArray = function( array, results ) {
8402 	array = Array.prototype.slice.call( array, 0 );
8403 
8404 	if ( results ) {
8405 		results.push.apply( results, array );
8406 		return results;
8407 	}
8408 
8409 	return array;
8410 };
8411 
8412 // Perform a simple check to determine if the browser is capable of
8413 // converting a NodeList to an array using builtin methods.
8414 // Also verifies that the returned array holds DOM nodes
8415 // (which is not the case in the Blackberry browser)
8416 try {
8417 	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
8418 
8419 // Provide a fallback method if it does not work
8420 } catch( e ) {
8421 	makeArray = function( array, results ) {
8422 		var i = 0,
8423 			ret = results || [];
8424 
8425 		if ( toString.call(array) === "[object Array]" ) {
8426 			Array.prototype.push.apply( ret, array );
8427 
8428 		} else {
8429 			if ( typeof array.length === "number" ) {
8430 				for ( var l = array.length; i < l; i++ ) {
8431 					ret.push( array[i] );
8432 				}
8433 
8434 			} else {
8435 				for ( ; array[i]; i++ ) {
8436 					ret.push( array[i] );
8437 				}
8438 			}
8439 		}
8440 
8441 		return ret;
8442 	};
8443 }
8444 
8445 var sortOrder, siblingCheck;
8446 
8447 if ( document.documentElement.compareDocumentPosition ) {
8448 	sortOrder = function( a, b ) {
8449 		if ( a === b ) {
8450 			hasDuplicate = true;
8451 			return 0;
8452 		}
8453 
8454 		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
8455 			return a.compareDocumentPosition ? -1 : 1;
8456 		}
8457 
8458 		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
8459 	};
8460 
8461 } else {
8462 	sortOrder = function( a, b ) {
8463 		// The nodes are identical, we can exit early
8464 		if ( a === b ) {
8465 			hasDuplicate = true;
8466 			return 0;
8467 
8468 		// Fallback to using sourceIndex (in IE) if it's available on both nodes
8469 		} else if ( a.sourceIndex && b.sourceIndex ) {
8470 			return a.sourceIndex - b.sourceIndex;
8471 		}
8472 
8473 		var al, bl,
8474 			ap = [],
8475 			bp = [],
8476 			aup = a.parentNode,
8477 			bup = b.parentNode,
8478 			cur = aup;
8479 
8480 		// If the nodes are siblings (or identical) we can do a quick check
8481 		if ( aup === bup ) {
8482 			return siblingCheck( a, b );
8483 
8484 		// If no parents were found then the nodes are disconnected
8485 		} else if ( !aup ) {
8486 			return -1;
8487 
8488 		} else if ( !bup ) {
8489 			return 1;
8490 		}
8491 
8492 		// Otherwise they're somewhere else in the tree so we need
8493 		// to build up a full list of the parentNodes for comparison
8494 		while ( cur ) {
8495 			ap.unshift( cur );
8496 			cur = cur.parentNode;
8497 		}
8498 
8499 		cur = bup;
8500 
8501 		while ( cur ) {
8502 			bp.unshift( cur );
8503 			cur = cur.parentNode;
8504 		}
8505 
8506 		al = ap.length;
8507 		bl = bp.length;
8508 
8509 		// Start walking down the tree looking for a discrepancy
8510 		for ( var i = 0; i < al && i < bl; i++ ) {
8511 			if ( ap[i] !== bp[i] ) {
8512 				return siblingCheck( ap[i], bp[i] );
8513 			}
8514 		}
8515 
8516 		// We ended someplace up the tree so do a sibling check
8517 		return i === al ?
8518 			siblingCheck( a, bp[i], -1 ) :
8519 			siblingCheck( ap[i], b, 1 );
8520 	};
8521 
8522 	siblingCheck = function( a, b, ret ) {
8523 		if ( a === b ) {
8524 			return ret;
8525 		}
8526 
8527 		var cur = a.nextSibling;
8528 
8529 		while ( cur ) {
8530 			if ( cur === b ) {
8531 				return -1;
8532 			}
8533 
8534 			cur = cur.nextSibling;
8535 		}
8536 
8537 		return 1;
8538 	};
8539 }
8540 
8541 // Check to see if the browser returns elements by name when
8542 // querying by getElementById (and provide a workaround)
8543 (function(){
8544 	// We're going to inject a fake input element with a specified name
8545 	var form = document.createElement("div"),
8546 		id = "script" + (new Date()).getTime(),
8547 		root = document.documentElement;
8548 
8549 	form.innerHTML = "<a name='" + id + "'/>";
8550 
8551 	// Inject it into the root element, check its status, and remove it quickly
8552 	root.insertBefore( form, root.firstChild );
8553 
8554 	// The workaround has to do additional checks after a getElementById
8555 	// Which slows things down for other browsers (hence the branching)
8556 	if ( document.getElementById( id ) ) {
8557 		Expr.find.ID = function( match, context, isXML ) {
8558 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
8559 				var m = context.getElementById(match[1]);
8560 
8561 				return m ?
8562 					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
8563 						[m] :
8564 						undefined :
8565 					[];
8566 			}
8567 		};
8568 
8569 		Expr.filter.ID = function( elem, match ) {
8570 			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
8571 
8572 			return elem.nodeType === 1 && node && node.nodeValue === match;
8573 		};
8574 	}
8575 
8576 	root.removeChild( form );
8577 
8578 	// release memory in IE
8579 	root = form = null;
8580 })();
8581 
8582 (function(){
8583 	// Check to see if the browser returns only elements
8584 	// when doing getElementsByTagName("*")
8585 
8586 	// Create a fake element
8587 	var div = document.createElement("div");
8588 	div.appendChild( document.createComment("") );
8589 
8590 	// Make sure no comments are found
8591 	if ( div.getElementsByTagName("*").length > 0 ) {
8592 		Expr.find.TAG = function( match, context ) {
8593 			var results = context.getElementsByTagName( match[1] );
8594 
8595 			// Filter out possible comments
8596 			if ( match[1] === "*" ) {
8597 				var tmp = [];
8598 
8599 				for ( var i = 0; results[i]; i++ ) {
8600 					if ( results[i].nodeType === 1 ) {
8601 						tmp.push( results[i] );
8602 					}
8603 				}
8604 
8605 				results = tmp;
8606 			}
8607 
8608 			return results;
8609 		};
8610 	}
8611 
8612 	// Check to see if an attribute returns normalized href attributes
8613 	div.innerHTML = "<a href='#'></a>";
8614 
8615 	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
8616 			div.firstChild.getAttribute("href") !== "#" ) {
8617 
8618 		Expr.attrHandle.href = function( elem ) {
8619 			return elem.getAttribute( "href", 2 );
8620 		};
8621 	}
8622 
8623 	// release memory in IE
8624 	div = null;
8625 })();
8626 
8627 if ( document.querySelectorAll ) {
8628 	(function(){
8629 		var oldSizzle = Sizzle,
8630 			div = document.createElement("div"),
8631 			id = "__sizzle__";
8632 
8633 		div.innerHTML = "<p class='TEST'></p>";
8634 
8635 		// Safari can't handle uppercase or unicode characters when
8636 		// in quirks mode.
8637 		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
8638 			return;
8639 		}
8640 
8641 		Sizzle = function( query, context, extra, seed ) {
8642 			context = context || document;
8643 
8644 			// Only use querySelectorAll on non-XML documents
8645 			// (ID selectors don't work in non-HTML documents)
8646 			if ( !seed && !Sizzle.isXML(context) ) {
8647 				// See if we find a selector to speed up
8648 				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
8649 
8650 				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
8651 					// Speed-up: Sizzle("TAG")
8652 					if ( match[1] ) {
8653 						return makeArray( context.getElementsByTagName( query ), extra );
8654 
8655 					// Speed-up: Sizzle(".CLASS")
8656 					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
8657 						return makeArray( context.getElementsByClassName( match[2] ), extra );
8658 					}
8659 				}
8660 
8661 				if ( context.nodeType === 9 ) {
8662 					// Speed-up: Sizzle("body")
8663 					// The body element only exists once, optimize finding it
8664 					if ( query === "body" && context.body ) {
8665 						return makeArray( [ context.body ], extra );
8666 
8667 					// Speed-up: Sizzle("#ID")
8668 					} else if ( match && match[3] ) {
8669 						var elem = context.getElementById( match[3] );
8670 
8671 						// Check parentNode to catch when Blackberry 4.6 returns
8672 						// nodes that are no longer in the document #6963
8673 						if ( elem && elem.parentNode ) {
8674 							// Handle the case where IE and Opera return items
8675 							// by name instead of ID
8676 							if ( elem.id === match[3] ) {
8677 								return makeArray( [ elem ], extra );
8678 							}
8679 
8680 						} else {
8681 							return makeArray( [], extra );
8682 						}
8683 					}
8684 
8685 					try {
8686 						return makeArray( context.querySelectorAll(query), extra );
8687 					} catch(qsaError) {}
8688 
8689 				// qSA works strangely on Element-rooted queries
8690 				// We can work around this by specifying an extra ID on the root
8691 				// and working up from there (Thanks to Andrew Dupont for the technique)
8692 				// IE 8 doesn't work on object elements
8693 				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
8694 					var oldContext = context,
8695 						old = context.getAttribute( "id" ),
8696 						nid = old || id,
8697 						hasParent = context.parentNode,
8698 						relativeHierarchySelector = /^\s*[+~]/.test( query );
8699 
8700 					if ( !old ) {
8701 						context.setAttribute( "id", nid );
8702 					} else {
8703 						nid = nid.replace( /'/g, "\\$&" );
8704 					}
8705 					if ( relativeHierarchySelector && hasParent ) {
8706 						context = context.parentNode;
8707 					}
8708 
8709 					try {
8710 						if ( !relativeHierarchySelector || hasParent ) {
8711 							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
8712 						}
8713 
8714 					} catch(pseudoError) {
8715 					} finally {
8716 						if ( !old ) {
8717 							oldContext.removeAttribute( "id" );
8718 						}
8719 					}
8720 				}
8721 			}
8722 
8723 			return oldSizzle(query, context, extra, seed);
8724 		};
8725 
8726 		for ( var prop in oldSizzle ) {
8727 			Sizzle[ prop ] = oldSizzle[ prop ];
8728 		}
8729 
8730 		// release memory in IE
8731 		div = null;
8732 	})();
8733 }
8734 
8735 (function(){
8736 	var html = document.documentElement,
8737 		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
8738 
8739 	if ( matches ) {
8740 		// Check to see if it's possible to do matchesSelector
8741 		// on a disconnected node (IE 9 fails this)
8742 		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
8743 			pseudoWorks = false;
8744 
8745 		try {
8746 			// This should fail with an exception
8747 			// Gecko does not error, returns false instead
8748 			matches.call( document.documentElement, "[test!='']:sizzle" );
8749 
8750 		} catch( pseudoError ) {
8751 			pseudoWorks = true;
8752 		}
8753 
8754 		Sizzle.matchesSelector = function( node, expr ) {
8755 			// Make sure that attribute selectors are quoted
8756 			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
8757 
8758 			if ( !Sizzle.isXML( node ) ) {
8759 				try {
8760 					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
8761 						var ret = matches.call( node, expr );
8762 
8763 						// IE 9's matchesSelector returns false on disconnected nodes
8764 						if ( ret || !disconnectedMatch ||
8765 								// As well, disconnected nodes are said to be in a document
8766 								// fragment in IE 9, so check for that
8767 								node.document && node.document.nodeType !== 11 ) {
8768 							return ret;
8769 						}
8770 					}
8771 				} catch(e) {}
8772 			}
8773 
8774 			return Sizzle(expr, null, null, [node]).length > 0;
8775 		};
8776 	}
8777 })();
8778 
8779 (function(){
8780 	var div = document.createElement("div");
8781 
8782 	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
8783 
8784 	// Opera can't find a second classname (in 9.6)
8785 	// Also, make sure that getElementsByClassName actually exists
8786 	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
8787 		return;
8788 	}
8789 
8790 	// Safari caches class attributes, doesn't catch changes (in 3.2)
8791 	div.lastChild.className = "e";
8792 
8793 	if ( div.getElementsByClassName("e").length === 1 ) {
8794 		return;
8795 	}
8796 
8797 	Expr.order.splice(1, 0, "CLASS");
8798 	Expr.find.CLASS = function( match, context, isXML ) {
8799 		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
8800 			return context.getElementsByClassName(match[1]);
8801 		}
8802 	};
8803 
8804 	// release memory in IE
8805 	div = null;
8806 })();
8807 
8808 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8809 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8810 		var elem = checkSet[i];
8811 
8812 		if ( elem ) {
8813 			var match = false;
8814 
8815 			elem = elem[dir];
8816 
8817 			while ( elem ) {
8818 				if ( elem[ expando ] === doneName ) {
8819 					match = checkSet[elem.sizset];
8820 					break;
8821 				}
8822 
8823 				if ( elem.nodeType === 1 && !isXML ){
8824 					elem[ expando ] = doneName;
8825 					elem.sizset = i;
8826 				}
8827 
8828 				if ( elem.nodeName.toLowerCase() === cur ) {
8829 					match = elem;
8830 					break;
8831 				}
8832 
8833 				elem = elem[dir];
8834 			}
8835 
8836 			checkSet[i] = match;
8837 		}
8838 	}
8839 }
8840 
8841 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8842 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8843 		var elem = checkSet[i];
8844 
8845 		if ( elem ) {
8846 			var match = false;
8847 
8848 			elem = elem[dir];
8849 
8850 			while ( elem ) {
8851 				if ( elem[ expando ] === doneName ) {
8852 					match = checkSet[elem.sizset];
8853 					break;
8854 				}
8855 
8856 				if ( elem.nodeType === 1 ) {
8857 					if ( !isXML ) {
8858 						elem[ expando ] = doneName;
8859 						elem.sizset = i;
8860 					}
8861 
8862 					if ( typeof cur !== "string" ) {
8863 						if ( elem === cur ) {
8864 							match = true;
8865 							break;
8866 						}
8867 
8868 					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
8869 						match = elem;
8870 						break;
8871 					}
8872 				}
8873 
8874 				elem = elem[dir];
8875 			}
8876 
8877 			checkSet[i] = match;
8878 		}
8879 	}
8880 }
8881 
8882 if ( document.documentElement.contains ) {
8883 	Sizzle.contains = function( a, b ) {
8884 		return a !== b && (a.contains ? a.contains(b) : true);
8885 	};
8886 
8887 } else if ( document.documentElement.compareDocumentPosition ) {
8888 	Sizzle.contains = function( a, b ) {
8889 		return !!(a.compareDocumentPosition(b) & 16);
8890 	};
8891 
8892 } else {
8893 	Sizzle.contains = function() {
8894 		return false;
8895 	};
8896 }
8897 
8898 Sizzle.isXML = function( elem ) {
8899 	// documentElement is verified for cases where it doesn't yet exist
8900 	// (such as loading iframes in IE - #4833)
8901 	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
8902 
8903 	return documentElement ? documentElement.nodeName !== "HTML" : false;
8904 };
8905 
8906 var posProcess = function( selector, context, seed ) {
8907 	var match,
8908 		tmpSet = [],
8909 		later = "",
8910 		root = context.nodeType ? [context] : context;
8911 
8912 	// Position selectors must be done after the filter
8913 	// And so must :not(positional) so we move all PSEUDOs to the end
8914 	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
8915 		later += match[0];
8916 		selector = selector.replace( Expr.match.PSEUDO, "" );
8917 	}
8918 
8919 	selector = Expr.relative[selector] ? selector + "*" : selector;
8920 
8921 	for ( var i = 0, l = root.length; i < l; i++ ) {
8922 		Sizzle( selector, root[i], tmpSet, seed );
8923 	}
8924 
8925 	return Sizzle.filter( later, tmpSet );
8926 };
8927 
8928 // EXPOSE
8929 
8930 window.tinymce.dom.Sizzle = Sizzle;
8931 
8932 })();
8933 
8934 
8935 (function(tinymce) {
8936 	tinymce.dom.Element = function(id, settings) {
8937 		var t = this, dom, el;
8938 
8939 		t.settings = settings = settings || {};
8940 		t.id = id;
8941 		t.dom = dom = settings.dom || tinymce.DOM;
8942 
8943 		// Only IE leaks DOM references, this is a lot faster
8944 		if (!tinymce.isIE)
8945 			el = dom.get(t.id);
8946 
8947 		tinymce.each(
8948 				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
8949 				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
8950 				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
8951 				'isHidden,setHTML,get').split(/,/), function(k) {
8952 					t[k] = function() {
8953 						var a = [id], i;
8954 
8955 						for (i = 0; i < arguments.length; i++)
8956 							a.push(arguments[i]);
8957 
8958 						a = dom[k].apply(dom, a);
8959 						t.update(k);
8960 
8961 						return a;
8962 					};
8963 			}
8964 		);
8965 
8966 		tinymce.extend(t, {
8967 			on : function(n, f, s) {
8968 				return tinymce.dom.Event.add(t.id, n, f, s);
8969 			},
8970 
8971 			getXY : function() {
8972 				return {
8973 					x : parseInt(t.getStyle('left')),
8974 					y : parseInt(t.getStyle('top'))
8975 				};
8976 			},
8977 
8978 			getSize : function() {
8979 				var n = dom.get(t.id);
8980 
8981 				return {
8982 					w : parseInt(t.getStyle('width') || n.clientWidth),
8983 					h : parseInt(t.getStyle('height') || n.clientHeight)
8984 				};
8985 			},
8986 
8987 			moveTo : function(x, y) {
8988 				t.setStyles({left : x, top : y});
8989 			},
8990 
8991 			moveBy : function(x, y) {
8992 				var p = t.getXY();
8993 
8994 				t.moveTo(p.x + x, p.y + y);
8995 			},
8996 
8997 			resizeTo : function(w, h) {
8998 				t.setStyles({width : w, height : h});
8999 			},
9000 
9001 			resizeBy : function(w, h) {
9002 				var s = t.getSize();
9003 
9004 				t.resizeTo(s.w + w, s.h + h);
9005 			},
9006 
9007 			update : function(k) {
9008 				var b;
9009 
9010 				if (tinymce.isIE6 && settings.blocker) {
9011 					k = k || '';
9012 
9013 					// Ignore getters
9014 					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
9015 						return;
9016 
9017 					// Remove blocker on remove
9018 					if (k == 'remove') {
9019 						dom.remove(t.blocker);
9020 						return;
9021 					}
9022 
9023 					if (!t.blocker) {
9024 						t.blocker = dom.uniqueId();
9025 						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
9026 						dom.setStyle(b, 'opacity', 0);
9027 					} else
9028 						b = dom.get(t.blocker);
9029 
9030 					dom.setStyles(b, {
9031 						left : t.getStyle('left', 1),
9032 						top : t.getStyle('top', 1),
9033 						width : t.getStyle('width', 1),
9034 						height : t.getStyle('height', 1),
9035 						display : t.getStyle('display', 1),
9036 						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
9037 					});
9038 				}
9039 			}
9040 		});
9041 	};
9042 })(tinymce);
9043 
9044 (function(tinymce) {
9045 	function trimNl(s) {
9046 		return s.replace(/[\n\r]+/g, '');
9047 	};
9048 
9049 	// Shorten names
9050 	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
9051 
9052 	tinymce.create('tinymce.dom.Selection', {
9053 		Selection : function(dom, win, serializer, editor) {
9054 			var t = this;
9055 
9056 			t.dom = dom;
9057 			t.win = win;
9058 			t.serializer = serializer;
9059 			t.editor = editor;
9060 
9061 			// Add events
9062 			each([
9063 				'onBeforeSetContent',
9064 
9065 				'onBeforeGetContent',
9066 
9067 				'onSetContent',
9068 
9069 				'onGetContent'
9070 			], function(e) {
9071 				t[e] = new tinymce.util.Dispatcher(t);
9072 			});
9073 
9074 			// No W3C Range support
9075 			if (!t.win.getSelection)
9076 				t.tridentSel = new tinymce.dom.TridentSelection(t);
9077 
9078 			if (tinymce.isIE && dom.boxModel)
9079 				this._fixIESelection();
9080 
9081 			// Prevent leaks
9082 			tinymce.addUnload(t.destroy, t);
9083 		},
9084 
9085 		setCursorLocation: function(node, offset) {
9086 			var t = this; var r = t.dom.createRng();
9087 			r.setStart(node, offset);
9088 			r.setEnd(node, offset);
9089 			t.setRng(r);
9090 			t.collapse(false);
9091 		},
9092 		getContent : function(s) {
9093 			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
9094 
9095 			s = s || {};
9096 			wb = wa = '';
9097 			s.get = true;
9098 			s.format = s.format || 'html';
9099 			s.forced_root_block = '';
9100 			t.onBeforeGetContent.dispatch(t, s);
9101 
9102 			if (s.format == 'text')
9103 				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
9104 
9105 			if (r.cloneContents) {
9106 				n = r.cloneContents();
9107 
9108 				if (n)
9109 					e.appendChild(n);
9110 			} else if (is(r.item) || is(r.htmlText)) {
9111 				// IE will produce invalid markup if elements are present that
9112 				// it doesn't understand like custom elements or HTML5 elements.
9113 				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
9114 				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
9115 				e.removeChild(e.firstChild);
9116 			} else
9117 				e.innerHTML = r.toString();
9118 
9119 			// Keep whitespace before and after
9120 			if (/^\s/.test(e.innerHTML))
9121 				wb = ' ';
9122 
9123 			if (/\s+$/.test(e.innerHTML))
9124 				wa = ' ';
9125 
9126 			s.getInner = true;
9127 
9128 			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
9129 			t.onGetContent.dispatch(t, s);
9130 
9131 			return s.content;
9132 		},
9133 
9134 		setContent : function(content, args) {
9135 			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
9136 
9137 			args = args || {format : 'html'};
9138 			args.set = true;
9139 			content = args.content = content;
9140 
9141 			// Dispatch before set content event
9142 			if (!args.no_events)
9143 				self.onBeforeSetContent.dispatch(self, args);
9144 
9145 			content = args.content;
9146 
9147 			if (rng.insertNode) {
9148 				// Make caret marker since insertNode places the caret in the beginning of text after insert
9149 				content += '<span id="__caret">_</span>';
9150 
9151 				// Delete and insert new node
9152 				if (rng.startContainer == doc && rng.endContainer == doc) {
9153 					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
9154 					doc.body.innerHTML = content;
9155 				} else {
9156 					rng.deleteContents();
9157 
9158 					if (doc.body.childNodes.length === 0) {
9159 						doc.body.innerHTML = content;
9160 					} else {
9161 						// createContextualFragment doesn't exists in IE 9 DOMRanges
9162 						if (rng.createContextualFragment) {
9163 							rng.insertNode(rng.createContextualFragment(content));
9164 						} else {
9165 							// Fake createContextualFragment call in IE 9
9166 							frag = doc.createDocumentFragment();
9167 							temp = doc.createElement('div');
9168 
9169 							frag.appendChild(temp);
9170 							temp.outerHTML = content;
9171 
9172 							rng.insertNode(frag);
9173 						}
9174 					}
9175 				}
9176 
9177 				// Move to caret marker
9178 				caretNode = self.dom.get('__caret');
9179 
9180 				// Make sure we wrap it compleatly, Opera fails with a simple select call
9181 				rng = doc.createRange();
9182 				rng.setStartBefore(caretNode);
9183 				rng.setEndBefore(caretNode);
9184 				self.setRng(rng);
9185 
9186 				// Remove the caret position
9187 				self.dom.remove('__caret');
9188 
9189 				try {
9190 					self.setRng(rng);
9191 				} catch (ex) {
9192 					// Might fail on Opera for some odd reason
9193 				}
9194 			} else {
9195 				if (rng.item) {
9196 					// Delete content and get caret text selection
9197 					doc.execCommand('Delete', false, null);
9198 					rng = self.getRng();
9199 				}
9200 
9201 				// Explorer removes spaces from the beginning of pasted contents
9202 				if (/^\s+/.test(content)) {
9203 					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
9204 					self.dom.remove('__mce_tmp');
9205 				} else
9206 					rng.pasteHTML(content);
9207 			}
9208 
9209 			// Dispatch set content event
9210 			if (!args.no_events)
9211 				self.onSetContent.dispatch(self, args);
9212 		},
9213 
9214 		getStart : function() {
9215 			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
9216 
9217 			if (rng.duplicate || rng.item) {
9218 				// Control selection, return first item
9219 				if (rng.item)
9220 					return rng.item(0);
9221 
9222 				// Get start element
9223 				checkRng = rng.duplicate();
9224 				checkRng.collapse(1);
9225 				startElement = checkRng.parentElement();
9226 				if (startElement.ownerDocument !== self.dom.doc) {
9227 					startElement = self.dom.getRoot();
9228 				}
9229 
9230 				// Check if range parent is inside the start element, then return the inner parent element
9231 				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
9232 				parentElement = node = rng.parentElement();
9233 				while (node = node.parentNode) {
9234 					if (node == startElement) {
9235 						startElement = parentElement;
9236 						break;
9237 					}
9238 				}
9239 
9240 				return startElement;
9241 			} else {
9242 				startElement = rng.startContainer;
9243 
9244 				if (startElement.nodeType == 1 && startElement.hasChildNodes())
9245 					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
9246 
9247 				if (startElement && startElement.nodeType == 3)
9248 					return startElement.parentNode;
9249 
9250 				return startElement;
9251 			}
9252 		},
9253 
9254 		getEnd : function() {
9255 			var self = this, rng = self.getRng(), endElement, endOffset;
9256 
9257 			if (rng.duplicate || rng.item) {
9258 				if (rng.item)
9259 					return rng.item(0);
9260 
9261 				rng = rng.duplicate();
9262 				rng.collapse(0);
9263 				endElement = rng.parentElement();
9264 				if (endElement.ownerDocument !== self.dom.doc) {
9265 					endElement = self.dom.getRoot();
9266 				}
9267 
9268 				if (endElement && endElement.nodeName == 'BODY')
9269 					return endElement.lastChild || endElement;
9270 
9271 				return endElement;
9272 			} else {
9273 				endElement = rng.endContainer;
9274 				endOffset = rng.endOffset;
9275 
9276 				if (endElement.nodeType == 1 && endElement.hasChildNodes())
9277 					endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
9278 
9279 				if (endElement && endElement.nodeType == 3)
9280 					return endElement.parentNode;
9281 
9282 				return endElement;
9283 			}
9284 		},
9285 
9286 		getBookmark : function(type, normalized) {
9287 			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
9288 
9289 			function findIndex(name, element) {
9290 				var index = 0;
9291 
9292 				each(dom.select(name), function(node, i) {
9293 					if (node == element)
9294 						index = i;
9295 				});
9296 
9297 				return index;
9298 			};
9299 
9300 			function normalizeTableCellSelection(rng) {
9301 				function moveEndPoint(start) {
9302 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
9303 
9304 					container = rng[prefix + 'Container'];
9305 					offset = rng[prefix + 'Offset'];
9306 
9307 					if (container.nodeType == 1 && container.nodeName == "TR") {
9308 						childNodes = container.childNodes;
9309 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9310 						if (container) {
9311 							offset = start ? 0 : container.childNodes.length;
9312 							rng['set' + (start ? 'Start' : 'End')](container, offset);
9313 						}
9314 					}
9315 				};
9316 
9317 				moveEndPoint(true);
9318 				moveEndPoint();
9319 
9320 				return rng;
9321 			};
9322 
9323 			function getLocation() {
9324 				var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
9325 
9326 				function getPoint(rng, start) {
9327 					var container = rng[start ? 'startContainer' : 'endContainer'],
9328 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
9329 
9330 					if (container.nodeType == 3) {
9331 						if (normalized) {
9332 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
9333 								offset += node.nodeValue.length;
9334 						}
9335 
9336 						point.push(offset);
9337 					} else {
9338 						childNodes = container.childNodes;
9339 
9340 						if (offset >= childNodes.length && childNodes.length) {
9341 							after = 1;
9342 							offset = Math.max(0, childNodes.length - 1);
9343 						}
9344 
9345 						point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
9346 					}
9347 
9348 					for (; container && container != root; container = container.parentNode)
9349 						point.push(t.dom.nodeIndex(container, normalized));
9350 
9351 					return point;
9352 				};
9353 
9354 				bookmark.start = getPoint(rng, true);
9355 
9356 				if (!t.isCollapsed())
9357 					bookmark.end = getPoint(rng);
9358 
9359 				return bookmark;
9360 			};
9361 
9362 			if (type == 2) {
9363 				if (t.tridentSel)
9364 					return t.tridentSel.getBookmark(type);
9365 
9366 				return getLocation();
9367 			}
9368 
9369 			// Handle simple range
9370 			if (type)
9371 				return {rng : t.getRng()};
9372 
9373 			rng = t.getRng();
9374 			id = dom.uniqueId();
9375 			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
9376 			styles = 'overflow:hidden;line-height:0px';
9377 
9378 			// Explorer method
9379 			if (rng.duplicate || rng.item) {
9380 				// Text selection
9381 				if (!rng.item) {
9382 					rng2 = rng.duplicate();
9383 
9384 					try {
9385 						// Insert start marker
9386 						rng.collapse();
9387 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
9388 
9389 						// Insert end marker
9390 						if (!collapsed) {
9391 							rng2.collapse(false);
9392 
9393 							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
9394 							rng.moveToElementText(rng2.parentElement());
9395 							if (rng.compareEndPoints('StartToEnd', rng2) === 0)
9396 								rng2.move('character', -1);
9397 
9398 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
9399 						}
9400 					} catch (ex) {
9401 						// IE might throw unspecified error so lets ignore it
9402 						return null;
9403 					}
9404 				} else {
9405 					// Control selection
9406 					element = rng.item(0);
9407 					name = element.nodeName;
9408 
9409 					return {name : name, index : findIndex(name, element)};
9410 				}
9411 			} else {
9412 				element = t.getNode();
9413 				name = element.nodeName;
9414 				if (name == 'IMG')
9415 					return {name : name, index : findIndex(name, element)};
9416 
9417 				// W3C method
9418 				rng2 = normalizeTableCellSelection(rng.cloneRange());
9419 
9420 				// Insert end marker
9421 				if (!collapsed) {
9422 					rng2.collapse(false);
9423 					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
9424 				}
9425 
9426 				rng = normalizeTableCellSelection(rng);
9427 				rng.collapse(true);
9428 				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
9429 			}
9430 
9431 			t.moveToBookmark({id : id, keep : 1});
9432 
9433 			return {id : id};
9434 		},
9435 
9436 		moveToBookmark : function(bookmark) {
9437 			var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
9438 
9439 			function setEndPoint(start) {
9440 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
9441 
9442 				if (point) {
9443 					offset = point[0];
9444 
9445 					// Find container node
9446 					for (node = root, i = point.length - 1; i >= 1; i--) {
9447 						children = node.childNodes;
9448 
9449 						if (point[i] > children.length - 1)
9450 							return;
9451 
9452 						node = children[point[i]];
9453 					}
9454 
9455 					// Move text offset to best suitable location
9456 					if (node.nodeType === 3)
9457 						offset = Math.min(point[0], node.nodeValue.length);
9458 
9459 					// Move element offset to best suitable location
9460 					if (node.nodeType === 1)
9461 						offset = Math.min(point[0], node.childNodes.length);
9462 
9463 					// Set offset within container node
9464 					if (start)
9465 						rng.setStart(node, offset);
9466 					else
9467 						rng.setEnd(node, offset);
9468 				}
9469 
9470 				return true;
9471 			};
9472 
9473 			function restoreEndPoint(suffix) {
9474 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
9475 
9476 				if (marker) {
9477 					node = marker.parentNode;
9478 
9479 					if (suffix == 'start') {
9480 						if (!keep) {
9481 							idx = dom.nodeIndex(marker);
9482 						} else {
9483 							node = marker.firstChild;
9484 							idx = 1;
9485 						}
9486 
9487 						startContainer = endContainer = node;
9488 						startOffset = endOffset = idx;
9489 					} else {
9490 						if (!keep) {
9491 							idx = dom.nodeIndex(marker);
9492 						} else {
9493 							node = marker.firstChild;
9494 							idx = 1;
9495 						}
9496 
9497 						endContainer = node;
9498 						endOffset = idx;
9499 					}
9500 
9501 					if (!keep) {
9502 						prev = marker.previousSibling;
9503 						next = marker.nextSibling;
9504 
9505 						// Remove all marker text nodes
9506 						each(tinymce.grep(marker.childNodes), function(node) {
9507 							if (node.nodeType == 3)
9508 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
9509 						});
9510 
9511 						// Remove marker but keep children if for example contents where inserted into the marker
9512 						// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
9513 						while (marker = dom.get(bookmark.id + '_' + suffix))
9514 							dom.remove(marker, 1);
9515 
9516 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
9517 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
9518 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
9519 							idx = prev.nodeValue.length;
9520 							prev.appendData(next.nodeValue);
9521 							dom.remove(next);
9522 
9523 							if (suffix == 'start') {
9524 								startContainer = endContainer = prev;
9525 								startOffset = endOffset = idx;
9526 							} else {
9527 								endContainer = prev;
9528 								endOffset = idx;
9529 							}
9530 						}
9531 					}
9532 				}
9533 			};
9534 
9535 			function addBogus(node) {
9536 				// Adds a bogus BR element for empty block elements
9537 				if (dom.isBlock(node) && !node.innerHTML && !isIE)
9538 					node.innerHTML = '<br data-mce-bogus="1" />';
9539 
9540 				return node;
9541 			};
9542 
9543 			if (bookmark) {
9544 				if (bookmark.start) {
9545 					rng = dom.createRng();
9546 					root = dom.getRoot();
9547 
9548 					if (t.tridentSel)
9549 						return t.tridentSel.moveToBookmark(bookmark);
9550 
9551 					if (setEndPoint(true) && setEndPoint()) {
9552 						t.setRng(rng);
9553 					}
9554 				} else if (bookmark.id) {
9555 					// Restore start/end points
9556 					restoreEndPoint('start');
9557 					restoreEndPoint('end');
9558 
9559 					if (startContainer) {
9560 						rng = dom.createRng();
9561 						rng.setStart(addBogus(startContainer), startOffset);
9562 						rng.setEnd(addBogus(endContainer), endOffset);
9563 						t.setRng(rng);
9564 					}
9565 				} else if (bookmark.name) {
9566 					t.select(dom.select(bookmark.name)[bookmark.index]);
9567 				} else if (bookmark.rng)
9568 					t.setRng(bookmark.rng);
9569 			}
9570 		},
9571 
9572 		select : function(node, content) {
9573 			var t = this, dom = t.dom, rng = dom.createRng(), idx;
9574 
9575 			function setPoint(node, start) {
9576 				var walker = new TreeWalker(node, node);
9577 
9578 				do {
9579 					// Text node
9580 					if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
9581 						if (start)
9582 							rng.setStart(node, 0);
9583 						else
9584 							rng.setEnd(node, node.nodeValue.length);
9585 
9586 						return;
9587 					}
9588 
9589 					// BR element
9590 					if (node.nodeName == 'BR') {
9591 						if (start)
9592 							rng.setStartBefore(node);
9593 						else
9594 							rng.setEndBefore(node);
9595 
9596 						return;
9597 					}
9598 				} while (node = (start ? walker.next() : walker.prev()));
9599 			};
9600 
9601 			if (node) {
9602 				idx = dom.nodeIndex(node);
9603 				rng.setStart(node.parentNode, idx);
9604 				rng.setEnd(node.parentNode, idx + 1);
9605 
9606 				// Find first/last text node or BR element
9607 				if (content) {
9608 					setPoint(node, 1);
9609 					setPoint(node);
9610 				}
9611 
9612 				t.setRng(rng);
9613 			}
9614 
9615 			return node;
9616 		},
9617 
9618 		isCollapsed : function() {
9619 			var t = this, r = t.getRng(), s = t.getSel();
9620 
9621 			if (!r || r.item)
9622 				return false;
9623 
9624 			if (r.compareEndPoints)
9625 				return r.compareEndPoints('StartToEnd', r) === 0;
9626 
9627 			return !s || r.collapsed;
9628 		},
9629 
9630 		collapse : function(to_start) {
9631 			var self = this, rng = self.getRng(), node;
9632 
9633 			// Control range on IE
9634 			if (rng.item) {
9635 				node = rng.item(0);
9636 				rng = self.win.document.body.createTextRange();
9637 				rng.moveToElementText(node);
9638 			}
9639 
9640 			rng.collapse(!!to_start);
9641 			self.setRng(rng);
9642 		},
9643 
9644 		getSel : function() {
9645 			var t = this, w = this.win;
9646 
9647 			return w.getSelection ? w.getSelection() : w.document.selection;
9648 		},
9649 
9650 		getRng : function(w3c) {
9651 			var self = this, selection, rng, elm, doc = self.win.document;
9652 
9653 			// Found tridentSel object then we need to use that one
9654 			if (w3c && self.tridentSel) {
9655 				return self.tridentSel.getRangeAt(0);
9656 			}
9657 
9658 			try {
9659 				if (selection = self.getSel()) {
9660 					rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
9661 				}
9662 			} catch (ex) {
9663 				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
9664 			}
9665 
9666 			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
9667 			if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
9668 				elm = doc.selection.createRange().item(0);
9669 				rng = doc.createRange();
9670 				rng.setStartBefore(elm);
9671 				rng.setEndAfter(elm);
9672 			}
9673 
9674 			// No range found then create an empty one
9675 			// This can occur when the editor is placed in a hidden container element on Gecko
9676 			// Or on IE when there was an exception
9677 			if (!rng) {
9678 				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
9679 			}
9680 
9681 			// If range is at start of document then move it to start of body
9682 			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
9683 				elm = self.dom.getRoot();
9684 				rng.setStart(elm, 0);
9685 				rng.setEnd(elm, 0);
9686 			}
9687 
9688 			if (self.selectedRange && self.explicitRange) {
9689 				if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
9690 					// Safari, Opera and Chrome only ever select text which causes the range to change.
9691 					// This lets us use the originally set range if the selection hasn't been changed by the user.
9692 					rng = self.explicitRange;
9693 				} else {
9694 					self.selectedRange = null;
9695 					self.explicitRange = null;
9696 				}
9697 			}
9698 
9699 			return rng;
9700 		},
9701 
9702 		setRng : function(r, forward) {
9703 			var s, t = this;
9704 
9705 			if (!t.tridentSel) {
9706 				s = t.getSel();
9707 
9708 				if (s) {
9709 					t.explicitRange = r;
9710 
9711 					try {
9712 						s.removeAllRanges();
9713 					} catch (ex) {
9714 						// IE9 might throw errors here don't know why
9715 					}
9716 
9717 					s.addRange(r);
9718 
9719 					// Forward is set to false and we have an extend function
9720 					if (forward === false && s.extend) {
9721 						s.collapse(r.endContainer, r.endOffset);
9722 						s.extend(r.startContainer, r.startOffset);
9723 					}
9724 
9725 					// adding range isn't always successful so we need to check range count otherwise an exception can occur
9726 					t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
9727 				}
9728 			} else {
9729 				// Is W3C Range
9730 				if (r.cloneRange) {
9731 					try {
9732 						t.tridentSel.addRange(r);
9733 						return;
9734 					} catch (ex) {
9735 						//IE9 throws an error here if called before selection is placed in the editor
9736 					}
9737 				}
9738 
9739 				// Is IE specific range
9740 				try {
9741 					r.select();
9742 				} catch (ex) {
9743 					// Needed for some odd IE bug #1843306
9744 				}
9745 			}
9746 		},
9747 
9748 		setNode : function(n) {
9749 			var t = this;
9750 
9751 			t.setContent(t.dom.getOuterHTML(n));
9752 
9753 			return n;
9754 		},
9755 
9756 		getNode : function() {
9757 			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
9758 
9759 			function skipEmptyTextNodes(n, forwards) {
9760 				var orig = n;
9761 				while (n && n.nodeType === 3 && n.length === 0) {
9762 					n = forwards ? n.nextSibling : n.previousSibling;
9763 				}
9764 				return n || orig;
9765 			};
9766 
9767 			// Range maybe lost after the editor is made visible again
9768 			if (!rng)
9769 				return t.dom.getRoot();
9770 
9771 			if (rng.setStart) {
9772 				elm = rng.commonAncestorContainer;
9773 
9774 				// Handle selection a image or other control like element such as anchors
9775 				if (!rng.collapsed) {
9776 					if (rng.startContainer == rng.endContainer) {
9777 						if (rng.endOffset - rng.startOffset < 2) {
9778 							if (rng.startContainer.hasChildNodes())
9779 								elm = rng.startContainer.childNodes[rng.startOffset];
9780 						}
9781 					}
9782 
9783 					// If the anchor node is a element instead of a text node then return this element
9784 					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
9785 					//	return sel.anchorNode.childNodes[sel.anchorOffset];
9786 
9787 					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
9788 					// This happens when you double click an underlined word in FireFox.
9789 					if (start.nodeType === 3 && end.nodeType === 3) {
9790 						if (start.length === rng.startOffset) {
9791 							start = skipEmptyTextNodes(start.nextSibling, true);
9792 						} else {
9793 							start = start.parentNode;
9794 						}
9795 						if (rng.endOffset === 0) {
9796 							end = skipEmptyTextNodes(end.previousSibling, false);
9797 						} else {
9798 							end = end.parentNode;
9799 						}
9800 
9801 						if (start && start === end)
9802 							return start;
9803 					}
9804 				}
9805 
9806 				if (elm && elm.nodeType == 3)
9807 					return elm.parentNode;
9808 
9809 				return elm;
9810 			}
9811 
9812 			return rng.item ? rng.item(0) : rng.parentElement();
9813 		},
9814 
9815 		getSelectedBlocks : function(st, en) {
9816 			var t = this, dom = t.dom, sb, eb, n, bl = [];
9817 
9818 			sb = dom.getParent(st || t.getStart(), dom.isBlock);
9819 			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
9820 
9821 			if (sb)
9822 				bl.push(sb);
9823 
9824 			if (sb && eb && sb != eb) {
9825 				n = sb;
9826 
9827 				var walker = new TreeWalker(sb, dom.getRoot());
9828 				while ((n = walker.next()) && n != eb) {
9829 					if (dom.isBlock(n))
9830 						bl.push(n);
9831 				}
9832 			}
9833 
9834 			if (eb && sb != eb)
9835 				bl.push(eb);
9836 
9837 			return bl;
9838 		},
9839 
9840 		isForward: function(){
9841 			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
9842 
9843 			// No support for selection direction then always return true
9844 			if (!sel || sel.anchorNode == null || sel.focusNode == null) {
9845 				return true;
9846 			}
9847 
9848 			anchorRange = dom.createRng();
9849 			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
9850 			anchorRange.collapse(true);
9851 
9852 			focusRange = dom.createRng();
9853 			focusRange.setStart(sel.focusNode, sel.focusOffset);
9854 			focusRange.collapse(true);
9855 
9856 			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
9857 		},
9858 
9859 		normalize : function() {
9860 			var self = this, rng, normalized, collapsed, node, sibling;
9861 
9862 			function normalizeEndPoint(start) {
9863 				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
9864 
9865 				function hasBrBeforeAfter(node, left) {
9866 					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
9867 
9868 					while (node = walker[left ? 'prev' : 'next']()) {
9869 						if (node.nodeName === "BR") {
9870 							return true;
9871 						}
9872 					}
9873 				};
9874 
9875 				// Walks the dom left/right to find a suitable text node to move the endpoint into
9876 				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
9877 				function findTextNodeRelative(left, startNode) {
9878 					var walker, lastInlineElement;
9879 
9880 					startNode = startNode || container;
9881 					walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
9882 
9883 					// Walk left until we hit a text node we can move to or a block/br/img
9884 					while (node = walker[left ? 'prev' : 'next']()) {
9885 						// Found text node that has a length
9886 						if (node.nodeType === 3 && node.nodeValue.length > 0) {
9887 							container = node;
9888 							offset = left ? node.nodeValue.length : 0;
9889 							normalized = true;
9890 							return;
9891 						}
9892 
9893 						// Break if we find a block or a BR/IMG/INPUT etc
9894 						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9895 							return;
9896 						}
9897 
9898 						lastInlineElement = node;
9899 					}
9900 
9901 					// Only fetch the last inline element when in caret mode for now
9902 					if (collapsed && lastInlineElement) {
9903 						container = lastInlineElement;
9904 						normalized = true;
9905 						offset = 0;
9906 					}
9907 				};
9908 
9909 				container = rng[(start ? 'start' : 'end') + 'Container'];
9910 				offset = rng[(start ? 'start' : 'end') + 'Offset'];
9911 				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
9912 
9913 				// If the container is a document move it to the body element
9914 				if (container.nodeType === 9) {
9915 					container = dom.getRoot();
9916 					offset = 0;
9917 				}
9918 
9919 				// If the container is body try move it into the closest text node or position
9920 				if (container === body) {
9921 					// If start is before/after a image, table etc
9922 					if (start) {
9923 						node = container.childNodes[offset > 0 ? offset - 1 : 0];
9924 						if (node) {
9925 							nodeName = node.nodeName.toLowerCase();
9926 							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
9927 								return;
9928 							}
9929 						}
9930 					}
9931 
9932 					// Resolve the index
9933 					if (container.hasChildNodes()) {
9934 						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
9935 						offset = 0;
9936 
9937 						// Don't walk into elements that doesn't have any child nodes like a IMG
9938 						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
9939 							// Walk the DOM to find a text node to place the caret at or a BR
9940 							node = container;
9941 							walker = new TreeWalker(container, body);
9942 
9943 							do {
9944 								// Found a text node use that position
9945 								if (node.nodeType === 3 && node.nodeValue.length > 0) {
9946 									offset = start ? 0 : node.nodeValue.length;
9947 									container = node;
9948 									normalized = true;
9949 									break;
9950 								}
9951 
9952 								// Found a BR/IMG element that we can place the caret before
9953 								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9954 									offset = dom.nodeIndex(node);
9955 									container = node.parentNode;
9956 
9957 									// Put caret after image when moving the end point
9958 									if (node.nodeName ==  "IMG" && !start) {
9959 										offset++;
9960 									}
9961 
9962 									normalized = true;
9963 									break;
9964 								}
9965 							} while (node = (start ? walker.next() : walker.prev()));
9966 						}
9967 					}
9968 				}
9969 
9970 				// Lean the caret to the left if possible
9971 				if (collapsed) {
9972 					// So this: <b>x</b><i>|x</i>
9973 					// Becomes: <b>x|</b><i>x</i>
9974 					// Seems that only gecko has issues with this
9975 					if (container.nodeType === 3 && offset === 0) {
9976 						findTextNodeRelative(true);
9977 					}
9978 
9979 					// Lean left into empty inline elements when the caret is before a BR
9980 					// So this: <i><b></b><i>|<br></i>
9981 					// Becomes: <i><b>|</b><i><br></i>
9982 					// Seems that only gecko has issues with this
9983 					if (container.nodeType === 1) {
9984 						node = container.childNodes[offset];
9985 						if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
9986 							findTextNodeRelative(true, container.childNodes[offset]);
9987 						}
9988 					}
9989 				}
9990 
9991 				// Lean the start of the selection right if possible
9992 				// So this: x[<b>x]</b>
9993 				// Becomes: x<b>[x]</b>
9994 				if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
9995 					findTextNodeRelative(false);
9996 				}
9997 
9998 				// Set endpoint if it was normalized
9999 				if (normalized)
10000 					rng['set' + (start ? 'Start' : 'End')](container, offset);
10001 			};
10002 
10003 			// Normalize only on non IE browsers for now
10004 			if (tinymce.isIE)
10005 				return;
10006 			
10007 			rng = self.getRng();
10008 			collapsed = rng.collapsed;
10009 
10010 			// Normalize the end points
10011 			normalizeEndPoint(true);
10012 
10013 			if (!collapsed)
10014 				normalizeEndPoint();
10015 
10016 			// Set the selection if it was normalized
10017 			if (normalized) {
10018 				// If it was collapsed then make sure it still is
10019 				if (collapsed) {
10020 					rng.collapse(true);
10021 				}
10022 
10023 				//console.log(self.dom.dumpRng(rng));
10024 				self.setRng(rng, self.isForward());
10025 			}
10026 		},
10027 
10028 		selectorChanged: function(selector, callback) {
10029 			var self = this, currentSelectors;
10030 
10031 			if (!self.selectorChangedData) {
10032 				self.selectorChangedData = {};
10033 				currentSelectors = {};
10034 
10035 				self.editor.onNodeChange.addToTop(function(ed, cm, node) {
10036 					var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
10037 
10038 					// Check for new matching selectors
10039 					each(self.selectorChangedData, function(callbacks, selector) {
10040 						each(parents, function(node) {
10041 							if (dom.is(node, selector)) {
10042 								if (!currentSelectors[selector]) {
10043 									// Execute callbacks
10044 									each(callbacks, function(callback) {
10045 										callback(true, {node: node, selector: selector, parents: parents});
10046 									});
10047 
10048 									currentSelectors[selector] = callbacks;
10049 								}
10050 
10051 								matchedSelectors[selector] = callbacks;
10052 								return false;
10053 							}
10054 						});
10055 					});
10056 
10057 					// Check if current selectors still match
10058 					each(currentSelectors, function(callbacks, selector) {
10059 						if (!matchedSelectors[selector]) {
10060 							delete currentSelectors[selector];
10061 
10062 							each(callbacks, function(callback) {
10063 								callback(false, {node: node, selector: selector, parents: parents});
10064 							});
10065 						}
10066 					});
10067 				});
10068 			}
10069 
10070 			// Add selector listeners
10071 			if (!self.selectorChangedData[selector]) {
10072 				self.selectorChangedData[selector] = [];
10073 			}
10074 
10075 			self.selectorChangedData[selector].push(callback);
10076 
10077 			return self;
10078 		},
10079 
10080 		destroy : function(manual) {
10081 			var self = this;
10082 
10083 			self.win = null;
10084 
10085 			// Manual destroy then remove unload handler
10086 			if (!manual)
10087 				tinymce.removeUnload(self.destroy);
10088 		},
10089 
10090 		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
10091 		_fixIESelection : function() {
10092 			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
10093 
10094 			// Return range from point or null if it failed
10095 			function rngFromPoint(x, y) {
10096 				var rng = body.createTextRange();
10097 
10098 				try {
10099 					rng.moveToPoint(x, y);
10100 				} catch (ex) {
10101 					// IE sometimes throws and exception, so lets just ignore it
10102 					rng = null;
10103 				}
10104 
10105 				return rng;
10106 			};
10107 
10108 			// Fires while the selection is changing
10109 			function selectionChange(e) {
10110 				var pointRng;
10111 
10112 				// Check if the button is down or not
10113 				if (e.button) {
10114 					// Create range from mouse position
10115 					pointRng = rngFromPoint(e.x, e.y);
10116 
10117 					if (pointRng) {
10118 						// Check if pointRange is before/after selection then change the endPoint
10119 						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
10120 							pointRng.setEndPoint('StartToStart', startRng);
10121 						else
10122 							pointRng.setEndPoint('EndToEnd', startRng);
10123 
10124 						pointRng.select();
10125 					}
10126 				} else
10127 					endSelection();
10128 			}
10129 
10130 			// Removes listeners
10131 			function endSelection() {
10132 				var rng = doc.selection.createRange();
10133 
10134 				// If the range is collapsed then use the last start range
10135 				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
10136 					startRng.select();
10137 
10138 				dom.unbind(doc, 'mouseup', endSelection);
10139 				dom.unbind(doc, 'mousemove', selectionChange);
10140 				startRng = started = 0;
10141 			};
10142 
10143 			// Make HTML element unselectable since we are going to handle selection by hand
10144 			doc.documentElement.unselectable = true;
10145 			
10146 			// Detect when user selects outside BODY
10147 			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
10148 				if (e.target.nodeName === 'HTML') {
10149 					if (started)
10150 						endSelection();
10151 
10152 					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
10153 					htmlElm = doc.documentElement;
10154 					if (htmlElm.scrollHeight > htmlElm.clientHeight)
10155 						return;
10156 
10157 					started = 1;
10158 					// Setup start position
10159 					startRng = rngFromPoint(e.x, e.y);
10160 					if (startRng) {
10161 						// Listen for selection change events
10162 						dom.bind(doc, 'mouseup', endSelection);
10163 						dom.bind(doc, 'mousemove', selectionChange);
10164 
10165 						dom.win.focus();
10166 						startRng.select();
10167 					}
10168 				}
10169 			});
10170 		}
10171 	});
10172 })(tinymce);
10173 
10174 (function(tinymce) {
10175 	tinymce.dom.Serializer = function(settings, dom, schema) {
10176 		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
10177 
10178 		// Support the old apply_source_formatting option
10179 		if (!settings.apply_source_formatting)
10180 			settings.indent = false;
10181 
10182 		// Default DOM and Schema if they are undefined
10183 		dom = dom || tinymce.DOM;
10184 		schema = schema || new tinymce.html.Schema(settings);
10185 		settings.entity_encoding = settings.entity_encoding || 'named';
10186 		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10187 
10188 		onPreProcess = new tinymce.util.Dispatcher(self);
10189 
10190 		onPostProcess = new tinymce.util.Dispatcher(self);
10191 
10192 		htmlParser = new tinymce.html.DomParser(settings, schema);
10193 
10194 		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10195 		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10196 			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10197 
10198 			while (i--) {
10199 				node = nodes[i];
10200 
10201 				value = node.attributes.map[internalName];
10202 				if (value !== undef) {
10203 					// Set external name to internal value and remove internal
10204 					node.attr(name, value.length > 0 ? value : null);
10205 					node.attr(internalName, null);
10206 				} else {
10207 					// No internal attribute found then convert the value we have in the DOM
10208 					value = node.attributes.map[name];
10209 
10210 					if (name === "style")
10211 						value = dom.serializeStyle(dom.parseStyle(value), node.name);
10212 					else if (urlConverter)
10213 						value = urlConverter.call(urlConverterScope, value, name, node.name);
10214 
10215 					node.attr(name, value.length > 0 ? value : null);
10216 				}
10217 			}
10218 		});
10219 
10220 		// Remove internal classes mceItem<..> or mceSelected
10221 		htmlParser.addAttributeFilter('class', function(nodes, name) {
10222 			var i = nodes.length, node, value;
10223 
10224 			while (i--) {
10225 				node = nodes[i];
10226 				value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
10227 				node.attr('class', value.length > 0 ? value : null);
10228 			}
10229 		});
10230 
10231 		// Remove bookmark elements
10232 		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10233 			var i = nodes.length, node;
10234 
10235 			while (i--) {
10236 				node = nodes[i];
10237 
10238 				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
10239 					node.remove();
10240 			}
10241 		});
10242 
10243 		// Remove expando attributes
10244 		htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
10245 			var i = nodes.length;
10246 
10247 			while (i--) {
10248 				nodes[i].attr(name, null);
10249 			}
10250 		});
10251 
10252 		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
10253 		htmlParser.addNodeFilter('script,style', function(nodes, name) {
10254 			var i = nodes.length, node, value;
10255 
10256 			function trim(value) {
10257 				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10258 						.replace(/^[\r\n]*|[\r\n]*$/g, '')
10259 						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10260 						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10261 			};
10262 
10263 			while (i--) {
10264 				node = nodes[i];
10265 				value = node.firstChild ? node.firstChild.value : '';
10266 
10267 				if (name === "script") {
10268 					// Remove mce- prefix from script elements
10269 					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
10270 
10271 					if (value.length > 0)
10272 						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10273 				} else {
10274 					if (value.length > 0)
10275 						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10276 				}
10277 			}
10278 		});
10279 
10280 		// Convert comments to cdata and handle protected comments
10281 		htmlParser.addNodeFilter('#comment', function(nodes, name) {
10282 			var i = nodes.length, node;
10283 
10284 			while (i--) {
10285 				node = nodes[i];
10286 
10287 				if (node.value.indexOf('[CDATA[') === 0) {
10288 					node.name = '#cdata';
10289 					node.type = 4;
10290 					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10291 				} else if (node.value.indexOf('mce:protected ') === 0) {
10292 					node.name = "#text";
10293 					node.type = 3;
10294 					node.raw = true;
10295 					node.value = unescape(node.value).substr(14);
10296 				}
10297 			}
10298 		});
10299 
10300 		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10301 			var i = nodes.length, node;
10302 
10303 			while (i--) {
10304 				node = nodes[i];
10305 				if (node.type === 7)
10306 					node.remove();
10307 				else if (node.type === 1) {
10308 					if (name === "input" && !("type" in node.attributes.map))
10309 						node.attr('type', 'text');
10310 				}
10311 			}
10312 		});
10313 
10314 		// Fix list elements, TODO: Replace this later
10315 		if (settings.fix_list_elements) {
10316 			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
10317 				var i = nodes.length, node, parentNode;
10318 
10319 				while (i--) {
10320 					node = nodes[i];
10321 					parentNode = node.parent;
10322 
10323 					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10324 						if (node.prev && node.prev.name === 'li') {
10325 							node.prev.append(node);
10326 						}
10327 					}
10328 				}
10329 			});
10330 		}
10331 
10332 		// Remove internal data attributes
10333 		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
10334 			var i = nodes.length;
10335 
10336 			while (i--) {
10337 				nodes[i].attr(name, null);
10338 			}
10339 		});
10340 
10341 		// Return public methods
10342 		return {
10343 			schema : schema,
10344 
10345 			addNodeFilter : htmlParser.addNodeFilter,
10346 
10347 			addAttributeFilter : htmlParser.addAttributeFilter,
10348 
10349 			onPreProcess : onPreProcess,
10350 
10351 			onPostProcess : onPostProcess,
10352 
10353 			serialize : function(node, args) {
10354 				var impl, doc, oldDoc, htmlSerializer, content;
10355 
10356 				// Explorer won't clone contents of script and style and the
10357 				// selected index of select elements are cleared on a clone operation.
10358 				if (isIE && dom.select('script,style,select,map').length > 0) {
10359 					content = node.innerHTML;
10360 					node = node.cloneNode(false);
10361 					dom.setHTML(node, content);
10362 				} else
10363 					node = node.cloneNode(true);
10364 
10365 				// Nodes needs to be attached to something in WebKit/Opera
10366 				// Older builds of Opera crashes if you attach the node to an document created dynamically
10367 				// and since we can't feature detect a crash we need to sniff the acutal build number
10368 				// This fix will make DOM ranges and make Sizzle happy!
10369 				impl = node.ownerDocument.implementation;
10370 				if (impl.createHTMLDocument) {
10371 					// Create an empty HTML document
10372 					doc = impl.createHTMLDocument("");
10373 
10374 					// Add the element or it's children if it's a body element to the new document
10375 					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
10376 						doc.body.appendChild(doc.importNode(node, true));
10377 					});
10378 
10379 					// Grab first child or body element for serialization
10380 					if (node.nodeName != 'BODY')
10381 						node = doc.body.firstChild;
10382 					else
10383 						node = doc.body;
10384 
10385 					// set the new document in DOMUtils so createElement etc works
10386 					oldDoc = dom.doc;
10387 					dom.doc = doc;
10388 				}
10389 
10390 				args = args || {};
10391 				args.format = args.format || 'html';
10392 
10393 				// Pre process
10394 				if (!args.no_events) {
10395 					args.node = node;
10396 					onPreProcess.dispatch(self, args);
10397 				}
10398 
10399 				// Setup serializer
10400 				htmlSerializer = new tinymce.html.Serializer(settings, schema);
10401 
10402 				// Parse and serialize HTML
10403 				args.content = htmlSerializer.serialize(
10404 					htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
10405 				);
10406 
10407 				// Replace all BOM characters for now until we can find a better solution
10408 				if (!args.cleanup)
10409 					args.content = args.content.replace(/\uFEFF|\u200B/g, '');
10410 
10411 				// Post process
10412 				if (!args.no_events)
10413 					onPostProcess.dispatch(self, args);
10414 
10415 				// Restore the old document if it was changed
10416 				if (oldDoc)
10417 					dom.doc = oldDoc;
10418 
10419 				args.node = null;
10420 
10421 				return args.content;
10422 			},
10423 
10424 			addRules : function(rules) {
10425 				schema.addValidElements(rules);
10426 			},
10427 
10428 			setRules : function(rules) {
10429 				schema.setValidElements(rules);
10430 			}
10431 		};
10432 	};
10433 })(tinymce);
10434 (function(tinymce) {
10435 	tinymce.dom.ScriptLoader = function(settings) {
10436 		var QUEUED = 0,
10437 			LOADING = 1,
10438 			LOADED = 2,
10439 			states = {},
10440 			queue = [],
10441 			scriptLoadedCallbacks = {},
10442 			queueLoadedCallbacks = [],
10443 			loading = 0,
10444 			undef;
10445 
10446 		function loadScript(url, callback) {
10447 			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
10448 
10449 			// Execute callback when script is loaded
10450 			function done() {
10451 				dom.remove(id);
10452 
10453 				if (elm)
10454 					elm.onreadystatechange = elm.onload = elm = null;
10455 
10456 				callback();
10457 			};
10458 			
10459 			function error() {
10460 				// Report the error so it's easier for people to spot loading errors
10461 				if (typeof(console) !== "undefined" && console.log)
10462 					console.log("Failed to load: " + url);
10463 
10464 				// We can't mark it as done if there is a load error since
10465 				// A) We don't want to produce 404 errors on the server and
10466 				// B) the onerror event won't fire on all browsers.
10467 				// done();
10468 			};
10469 
10470 			id = dom.uniqueId();
10471 
10472 			if (tinymce.isIE6) {
10473 				uri = new tinymce.util.URI(url);
10474 				loc = location;
10475 
10476 				// If script is from same domain and we
10477 				// use IE 6 then use XHR since it's more reliable
10478 				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
10479 					tinymce.util.XHR.send({
10480 						url : tinymce._addVer(uri.getURI()),
10481 						success : function(content) {
10482 							// Create new temp script element
10483 							var script = dom.create('script', {
10484 								type : 'text/javascript'
10485 							});
10486 
10487 							// Evaluate script in global scope
10488 							script.text = content;
10489 							document.getElementsByTagName('head')[0].appendChild(script);
10490 							dom.remove(script);
10491 
10492 							done();
10493 						},
10494 						
10495 						error : error
10496 					});
10497 
10498 					return;
10499 				}
10500 			}
10501 
10502 			// Create new script element
10503 			elm = document.createElement('script');
10504 			elm.id = id;
10505 			elm.type = 'text/javascript';
10506 			elm.src = tinymce._addVer(url);
10507 
10508 			// Add onload listener for non IE browsers since IE9
10509 			// fires onload event before the script is parsed and executed
10510 			if (!tinymce.isIE)
10511 				elm.onload = done;
10512 
10513 			// Add onerror event will get fired on some browsers but not all of them
10514 			elm.onerror = error;
10515 
10516 			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
10517 			if (!tinymce.isOpera) {
10518 				elm.onreadystatechange = function() {
10519 					var state = elm.readyState;
10520 
10521 					// Loaded state is passed on IE 6 however there
10522 					// are known issues with this method but we can't use
10523 					// XHR in a cross domain loading
10524 					if (state == 'complete' || state == 'loaded')
10525 						done();
10526 				};
10527 			}
10528 
10529 			// Most browsers support this feature so we report errors
10530 			// for those at least to help users track their missing plugins etc
10531 			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
10532 			/*elm.onerror = function() {
10533 				alert('Failed to load: ' + url);
10534 			};*/
10535 
10536 			// Add script to document
10537 			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
10538 		};
10539 
10540 		this.isDone = function(url) {
10541 			return states[url] == LOADED;
10542 		};
10543 
10544 		this.markDone = function(url) {
10545 			states[url] = LOADED;
10546 		};
10547 
10548 		this.add = this.load = function(url, callback, scope) {
10549 			var item, state = states[url];
10550 
10551 			// Add url to load queue
10552 			if (state == undef) {
10553 				queue.push(url);
10554 				states[url] = QUEUED;
10555 			}
10556 
10557 			if (callback) {
10558 				// Store away callback for later execution
10559 				if (!scriptLoadedCallbacks[url])
10560 					scriptLoadedCallbacks[url] = [];
10561 
10562 				scriptLoadedCallbacks[url].push({
10563 					func : callback,
10564 					scope : scope || this
10565 				});
10566 			}
10567 		};
10568 
10569 		this.loadQueue = function(callback, scope) {
10570 			this.loadScripts(queue, callback, scope);
10571 		};
10572 
10573 		this.loadScripts = function(scripts, callback, scope) {
10574 			var loadScripts;
10575 
10576 			function execScriptLoadedCallbacks(url) {
10577 				// Execute URL callback functions
10578 				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
10579 					callback.func.call(callback.scope);
10580 				});
10581 
10582 				scriptLoadedCallbacks[url] = undef;
10583 			};
10584 
10585 			queueLoadedCallbacks.push({
10586 				func : callback,
10587 				scope : scope || this
10588 			});
10589 
10590 			loadScripts = function() {
10591 				var loadingScripts = tinymce.grep(scripts);
10592 
10593 				// Current scripts has been handled
10594 				scripts.length = 0;
10595 
10596 				// Load scripts that needs to be loaded
10597 				tinymce.each(loadingScripts, function(url) {
10598 					// Script is already loaded then execute script callbacks directly
10599 					if (states[url] == LOADED) {
10600 						execScriptLoadedCallbacks(url);
10601 						return;
10602 					}
10603 
10604 					// Is script not loading then start loading it
10605 					if (states[url] != LOADING) {
10606 						states[url] = LOADING;
10607 						loading++;
10608 
10609 						loadScript(url, function() {
10610 							states[url] = LOADED;
10611 							loading--;
10612 
10613 							execScriptLoadedCallbacks(url);
10614 
10615 							// Load more scripts if they where added by the recently loaded script
10616 							loadScripts();
10617 						});
10618 					}
10619 				});
10620 
10621 				// No scripts are currently loading then execute all pending queue loaded callbacks
10622 				if (!loading) {
10623 					tinymce.each(queueLoadedCallbacks, function(callback) {
10624 						callback.func.call(callback.scope);
10625 					});
10626 
10627 					queueLoadedCallbacks.length = 0;
10628 				}
10629 			};
10630 
10631 			loadScripts();
10632 		};
10633 	};
10634 
10635 	// Global script loader
10636 	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
10637 })(tinymce);
10638 
10639 (function(tinymce) {
10640 	tinymce.dom.RangeUtils = function(dom) {
10641 		var INVISIBLE_CHAR = '\uFEFF';
10642 
10643 		this.walk = function(rng, callback) {
10644 			var startContainer = rng.startContainer,
10645 				startOffset = rng.startOffset,
10646 				endContainer = rng.endContainer,
10647 				endOffset = rng.endOffset,
10648 				ancestor, startPoint,
10649 				endPoint, node, parent, siblings, nodes;
10650 
10651 			// Handle table cell selection the table plugin enables
10652 			// you to fake select table cells and perform formatting actions on them
10653 			nodes = dom.select('td.mceSelected,th.mceSelected');
10654 			if (nodes.length > 0) {
10655 				tinymce.each(nodes, function(node) {
10656 					callback([node]);
10657 				});
10658 
10659 				return;
10660 			}
10661 
10662 			function exclude(nodes) {
10663 				var node;
10664 
10665 				// First node is excluded
10666 				node = nodes[0];
10667 				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
10668 					nodes.splice(0, 1);
10669 				}
10670 
10671 				// Last node is excluded
10672 				node = nodes[nodes.length - 1];
10673 				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
10674 					nodes.splice(nodes.length - 1, 1);
10675 				}
10676 
10677 				return nodes;
10678 			};
10679 
10680 			function collectSiblings(node, name, end_node) {
10681 				var siblings = [];
10682 
10683 				for (; node && node != end_node; node = node[name])
10684 					siblings.push(node);
10685 
10686 				return siblings;
10687 			};
10688 
10689 			function findEndPoint(node, root) {
10690 				do {
10691 					if (node.parentNode == root)
10692 						return node;
10693 
10694 					node = node.parentNode;
10695 				} while(node);
10696 			};
10697 
10698 			function walkBoundary(start_node, end_node, next) {
10699 				var siblingName = next ? 'nextSibling' : 'previousSibling';
10700 
10701 				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
10702 					parent = node.parentNode;
10703 					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
10704 
10705 					if (siblings.length) {
10706 						if (!next)
10707 							siblings.reverse();
10708 
10709 						callback(exclude(siblings));
10710 					}
10711 				}
10712 			};
10713 
10714 			// If index based start position then resolve it
10715 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
10716 				startContainer = startContainer.childNodes[startOffset];
10717 
10718 			// If index based end position then resolve it
10719 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
10720 				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
10721 
10722 			// Same container
10723 			if (startContainer == endContainer)
10724 				return callback(exclude([startContainer]));
10725 
10726 			// Find common ancestor and end points
10727 			ancestor = dom.findCommonAncestor(startContainer, endContainer);
10728 				
10729 			// Process left side
10730 			for (node = startContainer; node; node = node.parentNode) {
10731 				if (node === endContainer)
10732 					return walkBoundary(startContainer, ancestor, true);
10733 
10734 				if (node === ancestor)
10735 					break;
10736 			}
10737 
10738 			// Process right side
10739 			for (node = endContainer; node; node = node.parentNode) {
10740 				if (node === startContainer)
10741 					return walkBoundary(endContainer, ancestor);
10742 
10743 				if (node === ancestor)
10744 					break;
10745 			}
10746 
10747 			// Find start/end point
10748 			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
10749 			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
10750 
10751 			// Walk left leaf
10752 			walkBoundary(startContainer, startPoint, true);
10753 
10754 			// Walk the middle from start to end point
10755 			siblings = collectSiblings(
10756 				startPoint == startContainer ? startPoint : startPoint.nextSibling,
10757 				'nextSibling',
10758 				endPoint == endContainer ? endPoint.nextSibling : endPoint
10759 			);
10760 
10761 			if (siblings.length)
10762 				callback(exclude(siblings));
10763 
10764 			// Walk right leaf
10765 			walkBoundary(endContainer, endPoint);
10766 		};
10767 
10768 		this.split = function(rng) {
10769 			var startContainer = rng.startContainer,
10770 				startOffset = rng.startOffset,
10771 				endContainer = rng.endContainer,
10772 				endOffset = rng.endOffset;
10773 
10774 			function splitText(node, offset) {
10775 				return node.splitText(offset);
10776 			};
10777 
10778 			// Handle single text node
10779 			if (startContainer == endContainer && startContainer.nodeType == 3) {
10780 				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10781 					endContainer = splitText(startContainer, startOffset);
10782 					startContainer = endContainer.previousSibling;
10783 
10784 					if (endOffset > startOffset) {
10785 						endOffset = endOffset - startOffset;
10786 						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
10787 						endOffset = endContainer.nodeValue.length;
10788 						startOffset = 0;
10789 					} else {
10790 						endOffset = 0;
10791 					}
10792 				}
10793 			} else {
10794 				// Split startContainer text node if needed
10795 				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10796 					startContainer = splitText(startContainer, startOffset);
10797 					startOffset = 0;
10798 				}
10799 
10800 				// Split endContainer text node if needed
10801 				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
10802 					endContainer = splitText(endContainer, endOffset).previousSibling;
10803 					endOffset = endContainer.nodeValue.length;
10804 				}
10805 			}
10806 
10807 			return {
10808 				startContainer : startContainer,
10809 				startOffset : startOffset,
10810 				endContainer : endContainer,
10811 				endOffset : endOffset
10812 			};
10813 		};
10814 
10815 	};
10816 
10817 	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
10818 		if (rng1 && rng2) {
10819 			// Compare native IE ranges
10820 			if (rng1.item || rng1.duplicate) {
10821 				// Both are control ranges and the selected element matches
10822 				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
10823 					return true;
10824 
10825 				// Both are text ranges and the range matches
10826 				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
10827 					return true;
10828 			} else {
10829 				// Compare w3c ranges
10830 				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
10831 			}
10832 		}
10833 
10834 		return false;
10835 	};
10836 })(tinymce);
10837 
10838 (function(tinymce) {
10839 	var Event = tinymce.dom.Event, each = tinymce.each;
10840 
10841 	tinymce.create('tinymce.ui.KeyboardNavigation', {
10842 		KeyboardNavigation: function(settings, dom) {
10843 			var t = this, root = settings.root, items = settings.items,
10844 					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
10845 					excludeFromTabOrder = settings.excludeFromTabOrder,
10846 					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
10847 
10848 			dom = dom || tinymce.DOM;
10849 
10850 			itemFocussed = function(evt) {
10851 				focussedId = evt.target.id;
10852 			};
10853 			
10854 			itemBlurred = function(evt) {
10855 				dom.setAttrib(evt.target.id, 'tabindex', '-1');
10856 			};
10857 			
10858 			rootFocussed = function(evt) {
10859 				var item = dom.get(focussedId);
10860 				dom.setAttrib(item, 'tabindex', '0');
10861 				item.focus();
10862 			};
10863 			
10864 			t.focus = function() {
10865 				dom.get(focussedId).focus();
10866 			};
10867 
10868 			t.destroy = function() {
10869 				each(items, function(item) {
10870 					var elm = dom.get(item.id);
10871 
10872 					dom.unbind(elm, 'focus', itemFocussed);
10873 					dom.unbind(elm, 'blur', itemBlurred);
10874 				});
10875 
10876 				var rootElm = dom.get(root);
10877 				dom.unbind(rootElm, 'focus', rootFocussed);
10878 				dom.unbind(rootElm, 'keydown', rootKeydown);
10879 
10880 				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
10881 				t.destroy = function() {};
10882 			};
10883 			
10884 			t.moveFocus = function(dir, evt) {
10885 				var idx = -1, controls = t.controls, newFocus;
10886 
10887 				if (!focussedId)
10888 					return;
10889 
10890 				each(items, function(item, index) {
10891 					if (item.id === focussedId) {
10892 						idx = index;
10893 						return false;
10894 					}
10895 				});
10896 
10897 				idx += dir;
10898 				if (idx < 0) {
10899 					idx = items.length - 1;
10900 				} else if (idx >= items.length) {
10901 					idx = 0;
10902 				}
10903 				
10904 				newFocus = items[idx];
10905 				dom.setAttrib(focussedId, 'tabindex', '-1');
10906 				dom.setAttrib(newFocus.id, 'tabindex', '0');
10907 				dom.get(newFocus.id).focus();
10908 
10909 				if (settings.actOnFocus) {
10910 					settings.onAction(newFocus.id);
10911 				}
10912 
10913 				if (evt)
10914 					Event.cancel(evt);
10915 			};
10916 			
10917 			rootKeydown = function(evt) {
10918 				var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
10919 				
10920 				switch (evt.keyCode) {
10921 					case DOM_VK_LEFT:
10922 						if (enableLeftRight) t.moveFocus(-1);
10923 						break;
10924 	
10925 					case DOM_VK_RIGHT:
10926 						if (enableLeftRight) t.moveFocus(1);
10927 						break;
10928 	
10929 					case DOM_VK_UP:
10930 						if (enableUpDown) t.moveFocus(-1);
10931 						break;
10932 
10933 					case DOM_VK_DOWN:
10934 						if (enableUpDown) t.moveFocus(1);
10935 						break;
10936 
10937 					case DOM_VK_ESCAPE:
10938 						if (settings.onCancel) {
10939 							settings.onCancel();
10940 							Event.cancel(evt);
10941 						}
10942 						break;
10943 
10944 					case DOM_VK_ENTER:
10945 					case DOM_VK_RETURN:
10946 					case DOM_VK_SPACE:
10947 						if (settings.onAction) {
10948 							settings.onAction(focussedId);
10949 							Event.cancel(evt);
10950 						}
10951 						break;
10952 				}
10953 			};
10954 
10955 			// Set up state and listeners for each item.
10956 			each(items, function(item, idx) {
10957 				var tabindex, elm;
10958 
10959 				if (!item.id) {
10960 					item.id = dom.uniqueId('_mce_item_');
10961 				}
10962 
10963 				elm = dom.get(item.id);
10964 
10965 				if (excludeFromTabOrder) {
10966 					dom.bind(elm, 'blur', itemBlurred);
10967 					tabindex = '-1';
10968 				} else {
10969 					tabindex = (idx === 0 ? '0' : '-1');
10970 				}
10971 
10972 				elm.setAttribute('tabindex', tabindex);
10973 				dom.bind(elm, 'focus', itemFocussed);
10974 			});
10975 			
10976 			// Setup initial state for root element.
10977 			if (items[0]){
10978 				focussedId = items[0].id;
10979 			}
10980 
10981 			dom.setAttrib(root, 'tabindex', '-1');
10982 
10983 			// Setup listeners for root element.
10984 			var rootElm = dom.get(root);
10985 			dom.bind(rootElm, 'focus', rootFocussed);
10986 			dom.bind(rootElm, 'keydown', rootKeydown);
10987 		}
10988 	});
10989 })(tinymce);
10990 
10991 (function(tinymce) {
10992 	// Shorten class names
10993 	var DOM = tinymce.DOM, is = tinymce.is;
10994 
10995 	tinymce.create('tinymce.ui.Control', {
10996 		Control : function(id, s, editor) {
10997 			this.id = id;
10998 			this.settings = s = s || {};
10999 			this.rendered = false;
11000 			this.onRender = new tinymce.util.Dispatcher(this);
11001 			this.classPrefix = '';
11002 			this.scope = s.scope || this;
11003 			this.disabled = 0;
11004 			this.active = 0;
11005 			this.editor = editor;
11006 		},
11007 		
11008 		setAriaProperty : function(property, value) {
11009 			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
11010 			if (element) {
11011 				DOM.setAttrib(element, 'aria-' + property, !!value);
11012 			}
11013 		},
11014 		
11015 		focus : function() {
11016 			DOM.get(this.id).focus();
11017 		},
11018 
11019 		setDisabled : function(s) {
11020 			if (s != this.disabled) {
11021 				this.setAriaProperty('disabled', s);
11022 
11023 				this.setState('Disabled', s);
11024 				this.setState('Enabled', !s);
11025 				this.disabled = s;
11026 			}
11027 		},
11028 
11029 		isDisabled : function() {
11030 			return this.disabled;
11031 		},
11032 
11033 		setActive : function(s) {
11034 			if (s != this.active) {
11035 				this.setState('Active', s);
11036 				this.active = s;
11037 				this.setAriaProperty('pressed', s);
11038 			}
11039 		},
11040 
11041 		isActive : function() {
11042 			return this.active;
11043 		},
11044 
11045 		setState : function(c, s) {
11046 			var n = DOM.get(this.id);
11047 
11048 			c = this.classPrefix + c;
11049 
11050 			if (s)
11051 				DOM.addClass(n, c);
11052 			else
11053 				DOM.removeClass(n, c);
11054 		},
11055 
11056 		isRendered : function() {
11057 			return this.rendered;
11058 		},
11059 
11060 		renderHTML : function() {
11061 		},
11062 
11063 		renderTo : function(n) {
11064 			DOM.setHTML(n, this.renderHTML());
11065 		},
11066 
11067 		postRender : function() {
11068 			var t = this, b;
11069 
11070 			// Set pending states
11071 			if (is(t.disabled)) {
11072 				b = t.disabled;
11073 				t.disabled = -1;
11074 				t.setDisabled(b);
11075 			}
11076 
11077 			if (is(t.active)) {
11078 				b = t.active;
11079 				t.active = -1;
11080 				t.setActive(b);
11081 			}
11082 		},
11083 
11084 		remove : function() {
11085 			DOM.remove(this.id);
11086 			this.destroy();
11087 		},
11088 
11089 		destroy : function() {
11090 			tinymce.dom.Event.clear(this.id);
11091 		}
11092 	});
11093 })(tinymce);
11094 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
11095 	Container : function(id, s, editor) {
11096 		this.parent(id, s, editor);
11097 
11098 		this.controls = [];
11099 
11100 		this.lookup = {};
11101 	},
11102 
11103 	add : function(c) {
11104 		this.lookup[c.id] = c;
11105 		this.controls.push(c);
11106 
11107 		return c;
11108 	},
11109 
11110 	get : function(n) {
11111 		return this.lookup[n];
11112 	}
11113 });
11114 
11115 
11116 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
11117 	Separator : function(id, s) {
11118 		this.parent(id, s);
11119 		this.classPrefix = 'mceSeparator';
11120 		this.setDisabled(true);
11121 	},
11122 
11123 	renderHTML : function() {
11124 		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
11125 	}
11126 });
11127 
11128 (function(tinymce) {
11129 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11130 
11131 	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
11132 		MenuItem : function(id, s) {
11133 			this.parent(id, s);
11134 			this.classPrefix = 'mceMenuItem';
11135 		},
11136 
11137 		setSelected : function(s) {
11138 			this.setState('Selected', s);
11139 			this.setAriaProperty('checked', !!s);
11140 			this.selected = s;
11141 		},
11142 
11143 		isSelected : function() {
11144 			return this.selected;
11145 		},
11146 
11147 		postRender : function() {
11148 			var t = this;
11149 			
11150 			t.parent();
11151 
11152 			// Set pending state
11153 			if (is(t.selected))
11154 				t.setSelected(t.selected);
11155 		}
11156 	});
11157 })(tinymce);
11158 
11159 (function(tinymce) {
11160 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11161 
11162 	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
11163 		Menu : function(id, s) {
11164 			var t = this;
11165 
11166 			t.parent(id, s);
11167 			t.items = {};
11168 			t.collapsed = false;
11169 			t.menuCount = 0;
11170 			t.onAddItem = new tinymce.util.Dispatcher(this);
11171 		},
11172 
11173 		expand : function(d) {
11174 			var t = this;
11175 
11176 			if (d) {
11177 				walk(t, function(o) {
11178 					if (o.expand)
11179 						o.expand();
11180 				}, 'items', t);
11181 			}
11182 
11183 			t.collapsed = false;
11184 		},
11185 
11186 		collapse : function(d) {
11187 			var t = this;
11188 
11189 			if (d) {
11190 				walk(t, function(o) {
11191 					if (o.collapse)
11192 						o.collapse();
11193 				}, 'items', t);
11194 			}
11195 
11196 			t.collapsed = true;
11197 		},
11198 
11199 		isCollapsed : function() {
11200 			return this.collapsed;
11201 		},
11202 
11203 		add : function(o) {
11204 			if (!o.settings)
11205 				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
11206 
11207 			this.onAddItem.dispatch(this, o);
11208 
11209 			return this.items[o.id] = o;
11210 		},
11211 
11212 		addSeparator : function() {
11213 			return this.add({separator : true});
11214 		},
11215 
11216 		addMenu : function(o) {
11217 			if (!o.collapse)
11218 				o = this.createMenu(o);
11219 
11220 			this.menuCount++;
11221 
11222 			return this.add(o);
11223 		},
11224 
11225 		hasMenus : function() {
11226 			return this.menuCount !== 0;
11227 		},
11228 
11229 		remove : function(o) {
11230 			delete this.items[o.id];
11231 		},
11232 
11233 		removeAll : function() {
11234 			var t = this;
11235 
11236 			walk(t, function(o) {
11237 				if (o.removeAll)
11238 					o.removeAll();
11239 				else
11240 					o.remove();
11241 
11242 				o.destroy();
11243 			}, 'items', t);
11244 
11245 			t.items = {};
11246 		},
11247 
11248 		createMenu : function(o) {
11249 			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
11250 
11251 			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
11252 
11253 			return m;
11254 		}
11255 	});
11256 })(tinymce);
11257 (function(tinymce) {
11258 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
11259 
11260 	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
11261 		DropMenu : function(id, s) {
11262 			s = s || {};
11263 			s.container = s.container || DOM.doc.body;
11264 			s.offset_x = s.offset_x || 0;
11265 			s.offset_y = s.offset_y || 0;
11266 			s.vp_offset_x = s.vp_offset_x || 0;
11267 			s.vp_offset_y = s.vp_offset_y || 0;
11268 
11269 			if (is(s.icons) && !s.icons)
11270 				s['class'] += ' mceNoIcons';
11271 
11272 			this.parent(id, s);
11273 			this.onShowMenu = new tinymce.util.Dispatcher(this);
11274 			this.onHideMenu = new tinymce.util.Dispatcher(this);
11275 			this.classPrefix = 'mceMenu';
11276 		},
11277 
11278 		createMenu : function(s) {
11279 			var t = this, cs = t.settings, m;
11280 
11281 			s.container = s.container || cs.container;
11282 			s.parent = t;
11283 			s.constrain = s.constrain || cs.constrain;
11284 			s['class'] = s['class'] || cs['class'];
11285 			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
11286 			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
11287 			s.keyboard_focus = cs.keyboard_focus;
11288 			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
11289 
11290 			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
11291 
11292 			return m;
11293 		},
11294 		
11295 		focus : function() {
11296 			var t = this;
11297 			if (t.keyboardNav) {
11298 				t.keyboardNav.focus();
11299 			}
11300 		},
11301 
11302 		update : function() {
11303 			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
11304 
11305 			tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
11306 			th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
11307 
11308 			if (!DOM.boxModel)
11309 				t.element.setStyles({width : tw + 2, height : th + 2});
11310 			else
11311 				t.element.setStyles({width : tw, height : th});
11312 
11313 			if (s.max_width)
11314 				DOM.setStyle(co, 'width', tw);
11315 
11316 			if (s.max_height) {
11317 				DOM.setStyle(co, 'height', th);
11318 
11319 				if (tb.clientHeight < s.max_height)
11320 					DOM.setStyle(co, 'overflow', 'hidden');
11321 			}
11322 		},
11323 
11324 		showMenu : function(x, y, px) {
11325 			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
11326 
11327 			t.collapse(1);
11328 
11329 			if (t.isMenuVisible)
11330 				return;
11331 
11332 			if (!t.rendered) {
11333 				co = DOM.add(t.settings.container, t.renderNode());
11334 
11335 				each(t.items, function(o) {
11336 					o.postRender();
11337 				});
11338 
11339 				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11340 			} else
11341 				co = DOM.get('menu_' + t.id);
11342 
11343 			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
11344 			if (!tinymce.isOpera)
11345 				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
11346 
11347 			DOM.show(co);
11348 			t.update();
11349 
11350 			x += s.offset_x || 0;
11351 			y += s.offset_y || 0;
11352 			vp.w -= 4;
11353 			vp.h -= 4;
11354 
11355 			// Move inside viewport if not submenu
11356 			if (s.constrain) {
11357 				w = co.clientWidth - ot;
11358 				h = co.clientHeight - ot;
11359 				mx = vp.x + vp.w;
11360 				my = vp.y + vp.h;
11361 
11362 				if ((x + s.vp_offset_x + w) > mx)
11363 					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
11364 
11365 				if ((y + s.vp_offset_y + h) > my)
11366 					y = Math.max(0, (my - s.vp_offset_y) - h);
11367 			}
11368 
11369 			DOM.setStyles(co, {left : x , top : y});
11370 			t.element.update();
11371 
11372 			t.isMenuVisible = 1;
11373 			t.mouseClickFunc = Event.add(co, 'click', function(e) {
11374 				var m;
11375 
11376 				e = e.target;
11377 
11378 				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
11379 					m = t.items[e.id];
11380 
11381 					if (m.isDisabled())
11382 						return;
11383 
11384 					dm = t;
11385 
11386 					while (dm) {
11387 						if (dm.hideMenu)
11388 							dm.hideMenu();
11389 
11390 						dm = dm.settings.parent;
11391 					}
11392 
11393 					if (m.settings.onclick)
11394 						m.settings.onclick(e);
11395 
11396 					return false; // Cancel to fix onbeforeunload problem
11397 				}
11398 			});
11399 
11400 			if (t.hasMenus()) {
11401 				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
11402 					var m, r, mi;
11403 
11404 					e = e.target;
11405 					if (e && (e = DOM.getParent(e, 'tr'))) {
11406 						m = t.items[e.id];
11407 
11408 						if (t.lastMenu)
11409 							t.lastMenu.collapse(1);
11410 
11411 						if (m.isDisabled())
11412 							return;
11413 
11414 						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
11415 							//p = DOM.getPos(s.container);
11416 							r = DOM.getRect(e);
11417 							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
11418 							t.lastMenu = m;
11419 							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
11420 						}
11421 					}
11422 				});
11423 			}
11424 			
11425 			Event.add(co, 'keydown', t._keyHandler, t);
11426 
11427 			t.onShowMenu.dispatch(t);
11428 
11429 			if (s.keyboard_focus) { 
11430 				t._setupKeyboardNav(); 
11431 			}
11432 		},
11433 
11434 		hideMenu : function(c) {
11435 			var t = this, co = DOM.get('menu_' + t.id), e;
11436 
11437 			if (!t.isMenuVisible)
11438 				return;
11439 
11440 			if (t.keyboardNav) t.keyboardNav.destroy();
11441 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11442 			Event.remove(co, 'click', t.mouseClickFunc);
11443 			Event.remove(co, 'keydown', t._keyHandler);
11444 			DOM.hide(co);
11445 			t.isMenuVisible = 0;
11446 
11447 			if (!c)
11448 				t.collapse(1);
11449 
11450 			if (t.element)
11451 				t.element.hide();
11452 
11453 			if (e = DOM.get(t.id))
11454 				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
11455 
11456 			t.onHideMenu.dispatch(t);
11457 		},
11458 
11459 		add : function(o) {
11460 			var t = this, co;
11461 
11462 			o = t.parent(o);
11463 
11464 			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
11465 				t._add(DOM.select('tbody', co)[0], o);
11466 
11467 			return o;
11468 		},
11469 
11470 		collapse : function(d) {
11471 			this.parent(d);
11472 			this.hideMenu(1);
11473 		},
11474 
11475 		remove : function(o) {
11476 			DOM.remove(o.id);
11477 			this.destroy();
11478 
11479 			return this.parent(o);
11480 		},
11481 
11482 		destroy : function() {
11483 			var t = this, co = DOM.get('menu_' + t.id);
11484 
11485 			if (t.keyboardNav) t.keyboardNav.destroy();
11486 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11487 			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
11488 			Event.remove(co, 'click', t.mouseClickFunc);
11489 			Event.remove(co, 'keydown', t._keyHandler);
11490 
11491 			if (t.element)
11492 				t.element.remove();
11493 
11494 			DOM.remove(co);
11495 		},
11496 
11497 		renderNode : function() {
11498 			var t = this, s = t.settings, n, tb, co, w;
11499 
11500 			w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
11501 			if (t.settings.parent) {
11502 				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
11503 			}
11504 			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
11505 			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11506 
11507 			if (s.menu_line)
11508 				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
11509 
11510 //			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
11511 			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
11512 			tb = DOM.add(n, 'tbody');
11513 
11514 			each(t.items, function(o) {
11515 				t._add(tb, o);
11516 			});
11517 
11518 			t.rendered = true;
11519 
11520 			return w;
11521 		},
11522 
11523 		// Internal functions
11524 		_setupKeyboardNav : function(){
11525 			var contextMenu, menuItems, t=this; 
11526 			contextMenu = DOM.get('menu_' + t.id);
11527 			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
11528 			menuItems.splice(0,0,contextMenu);
11529 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11530 				root: 'menu_' + t.id,
11531 				items: menuItems,
11532 				onCancel: function() {
11533 					t.hideMenu();
11534 				},
11535 				enableUpDown: true
11536 			});
11537 			contextMenu.focus();
11538 		},
11539 
11540 		_keyHandler : function(evt) {
11541 			var t = this, e;
11542 			switch (evt.keyCode) {
11543 				case 37: // Left
11544 					if (t.settings.parent) {
11545 						t.hideMenu();
11546 						t.settings.parent.focus();
11547 						Event.cancel(evt);
11548 					}
11549 					break;
11550 				case 39: // Right
11551 					if (t.mouseOverFunc)
11552 						t.mouseOverFunc(evt);
11553 					break;
11554 			}
11555 		},
11556 
11557 		_add : function(tb, o) {
11558 			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
11559 
11560 			if (s.separator) {
11561 				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
11562 				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
11563 
11564 				if (n = ro.previousSibling)
11565 					DOM.addClass(n, 'mceLast');
11566 
11567 				return;
11568 			}
11569 
11570 			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
11571 			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
11572 			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
11573 
11574 			if (s.parent) {
11575 				DOM.setAttrib(a, 'aria-haspopup', 'true');
11576 				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
11577 			}
11578 
11579 			DOM.addClass(it, s['class']);
11580 //			n = DOM.add(n, 'span', {'class' : 'item'});
11581 
11582 			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
11583 
11584 			if (s.icon_src)
11585 				DOM.add(ic, 'img', {src : s.icon_src});
11586 
11587 			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
11588 
11589 			if (o.settings.style) {
11590 				if (typeof o.settings.style == "function")
11591 					o.settings.style = o.settings.style();
11592 
11593 				DOM.setAttrib(n, 'style', o.settings.style);
11594 			}
11595 
11596 			if (tb.childNodes.length == 1)
11597 				DOM.addClass(ro, 'mceFirst');
11598 
11599 			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
11600 				DOM.addClass(ro, 'mceFirst');
11601 
11602 			if (o.collapse)
11603 				DOM.addClass(ro, cp + 'ItemSub');
11604 
11605 			if (n = ro.previousSibling)
11606 				DOM.removeClass(n, 'mceLast');
11607 
11608 			DOM.addClass(ro, 'mceLast');
11609 		}
11610 	});
11611 })(tinymce);
11612 (function(tinymce) {
11613 	var DOM = tinymce.DOM;
11614 
11615 	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
11616 		Button : function(id, s, ed) {
11617 			this.parent(id, s, ed);
11618 			this.classPrefix = 'mceButton';
11619 		},
11620 
11621 		renderHTML : function() {
11622 			var cp = this.classPrefix, s = this.settings, h, l;
11623 
11624 			l = DOM.encode(s.label || '');
11625 			h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
11626 			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
11627 				h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11628 			else
11629 				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11630 
11631 			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
11632 			h += '</a>';
11633 			return h;
11634 		},
11635 
11636 		postRender : function() {
11637 			var t = this, s = t.settings, imgBookmark;
11638 
11639 			// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
11640 			// need to keep the selection in case the selection is lost
11641 			if (tinymce.isIE && t.editor) {
11642 				tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
11643 					var nodeName = t.editor.selection.getNode().nodeName;
11644 					imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
11645 				});
11646 			}
11647 			tinymce.dom.Event.add(t.id, 'click', function(e) {
11648 				if (!t.isDisabled()) {
11649 					// restore the selection in case the selection is lost in IE
11650 					if (tinymce.isIE && t.editor && imgBookmark !== null) {
11651 						t.editor.selection.moveToBookmark(imgBookmark);
11652 					}
11653 					return s.onclick.call(s.scope, e);
11654 				}
11655 			});
11656 			tinymce.dom.Event.add(t.id, 'keyup', function(e) {
11657 				if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
11658 					return s.onclick.call(s.scope, e);
11659 			});
11660 		}
11661 	});
11662 })(tinymce);
11663 
11664 (function(tinymce) {
11665 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11666 
11667 	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
11668 		ListBox : function(id, s, ed) {
11669 			var t = this;
11670 
11671 			t.parent(id, s, ed);
11672 
11673 			t.items = [];
11674 
11675 			t.onChange = new Dispatcher(t);
11676 
11677 			t.onPostRender = new Dispatcher(t);
11678 
11679 			t.onAdd = new Dispatcher(t);
11680 
11681 			t.onRenderMenu = new tinymce.util.Dispatcher(this);
11682 
11683 			t.classPrefix = 'mceListBox';
11684 			t.marked = {};
11685 		},
11686 
11687 		select : function(va) {
11688 			var t = this, fv, f;
11689 
11690 			t.marked = {};
11691 
11692 			if (va == undef)
11693 				return t.selectByIndex(-1);
11694 
11695 			// Is string or number make function selector
11696 			if (va && typeof(va)=="function")
11697 				f = va;
11698 			else {
11699 				f = function(v) {
11700 					return v == va;
11701 				};
11702 			}
11703 
11704 			// Do we need to do something?
11705 			if (va != t.selectedValue) {
11706 				// Find item
11707 				each(t.items, function(o, i) {
11708 					if (f(o.value)) {
11709 						fv = 1;
11710 						t.selectByIndex(i);
11711 						return false;
11712 					}
11713 				});
11714 
11715 				if (!fv)
11716 					t.selectByIndex(-1);
11717 			}
11718 		},
11719 
11720 		selectByIndex : function(idx) {
11721 			var t = this, e, o, label;
11722 
11723 			t.marked = {};
11724 
11725 			if (idx != t.selectedIndex) {
11726 				e = DOM.get(t.id + '_text');
11727 				label = DOM.get(t.id + '_voiceDesc');
11728 				o = t.items[idx];
11729 
11730 				if (o) {
11731 					t.selectedValue = o.value;
11732 					t.selectedIndex = idx;
11733 					DOM.setHTML(e, DOM.encode(o.title));
11734 					DOM.setHTML(label, t.settings.title + " - " + o.title);
11735 					DOM.removeClass(e, 'mceTitle');
11736 					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
11737 				} else {
11738 					DOM.setHTML(e, DOM.encode(t.settings.title));
11739 					DOM.setHTML(label, DOM.encode(t.settings.title));
11740 					DOM.addClass(e, 'mceTitle');
11741 					t.selectedValue = t.selectedIndex = null;
11742 					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
11743 				}
11744 				e = 0;
11745 			}
11746 		},
11747 
11748 		mark : function(value) {
11749 			this.marked[value] = true;
11750 		},
11751 
11752 		add : function(n, v, o) {
11753 			var t = this;
11754 
11755 			o = o || {};
11756 			o = tinymce.extend(o, {
11757 				title : n,
11758 				value : v
11759 			});
11760 
11761 			t.items.push(o);
11762 			t.onAdd.dispatch(t, o);
11763 		},
11764 
11765 		getLength : function() {
11766 			return this.items.length;
11767 		},
11768 
11769 		renderHTML : function() {
11770 			var h = '', t = this, s = t.settings, cp = t.classPrefix;
11771 
11772 			h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
11773 			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
11774 			h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
11775 			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
11776 			h += '</tr></tbody></table></span>';
11777 
11778 			return h;
11779 		},
11780 
11781 		showMenu : function() {
11782 			var t = this, p2, e = DOM.get(this.id), m;
11783 
11784 			if (t.isDisabled() || t.items.length === 0)
11785 				return;
11786 
11787 			if (t.menu && t.menu.isMenuVisible)
11788 				return t.hideMenu();
11789 
11790 			if (!t.isMenuRendered) {
11791 				t.renderMenu();
11792 				t.isMenuRendered = true;
11793 			}
11794 
11795 			p2 = DOM.getPos(e);
11796 
11797 			m = t.menu;
11798 			m.settings.offset_x = p2.x;
11799 			m.settings.offset_y = p2.y;
11800 			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
11801 
11802 			// Select in menu
11803 			each(t.items, function(o) {
11804 				if (m.items[o.id]) {
11805 					m.items[o.id].setSelected(0);
11806 				}
11807 			});
11808 
11809 			each(t.items, function(o) {
11810 				if (m.items[o.id] && t.marked[o.value]) {
11811 					m.items[o.id].setSelected(1);
11812 				}
11813 
11814 				if (o.value === t.selectedValue) {
11815 					m.items[o.id].setSelected(1);
11816 				}
11817 			});
11818 
11819 			m.showMenu(0, e.clientHeight);
11820 
11821 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
11822 			DOM.addClass(t.id, t.classPrefix + 'Selected');
11823 
11824 			//DOM.get(t.id + '_text').focus();
11825 		},
11826 
11827 		hideMenu : function(e) {
11828 			var t = this;
11829 
11830 			if (t.menu && t.menu.isMenuVisible) {
11831 				DOM.removeClass(t.id, t.classPrefix + 'Selected');
11832 
11833 				// Prevent double toogles by canceling the mouse click event to the button
11834 				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
11835 					return;
11836 
11837 				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
11838 					DOM.removeClass(t.id, t.classPrefix + 'Selected');
11839 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
11840 					t.menu.hideMenu();
11841 				}
11842 			}
11843 		},
11844 
11845 		renderMenu : function() {
11846 			var t = this, m;
11847 
11848 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
11849 				menu_line : 1,
11850 				'class' : t.classPrefix + 'Menu mceNoIcons',
11851 				max_width : 250,
11852 				max_height : 150
11853 			});
11854 
11855 			m.onHideMenu.add(function() {
11856 				t.hideMenu();
11857 				t.focus();
11858 			});
11859 
11860 			m.add({
11861 				title : t.settings.title,
11862 				'class' : 'mceMenuItemTitle',
11863 				onclick : function() {
11864 					if (t.settings.onselect('') !== false)
11865 						t.select(''); // Must be runned after
11866 				}
11867 			});
11868 
11869 			each(t.items, function(o) {
11870 				// No value then treat it as a title
11871 				if (o.value === undef) {
11872 					m.add({
11873 						title : o.title,
11874 						role : "option",
11875 						'class' : 'mceMenuItemTitle',
11876 						onclick : function() {
11877 							if (t.settings.onselect('') !== false)
11878 								t.select(''); // Must be runned after
11879 						}
11880 					});
11881 				} else {
11882 					o.id = DOM.uniqueId();
11883 					o.role= "option";
11884 					o.onclick = function() {
11885 						if (t.settings.onselect(o.value) !== false)
11886 							t.select(o.value); // Must be runned after
11887 					};
11888 
11889 					m.add(o);
11890 				}
11891 			});
11892 
11893 			t.onRenderMenu.dispatch(t, m);
11894 			t.menu = m;
11895 		},
11896 
11897 		postRender : function() {
11898 			var t = this, cp = t.classPrefix;
11899 
11900 			Event.add(t.id, 'click', t.showMenu, t);
11901 			Event.add(t.id, 'keydown', function(evt) {
11902 				if (evt.keyCode == 32) { // Space
11903 					t.showMenu(evt);
11904 					Event.cancel(evt);
11905 				}
11906 			});
11907 			Event.add(t.id, 'focus', function() {
11908 				if (!t._focused) {
11909 					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
11910 						if (e.keyCode == 40) {
11911 							t.showMenu();
11912 							Event.cancel(e);
11913 						}
11914 					});
11915 					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
11916 						var v;
11917 						if (e.keyCode == 13) {
11918 							// Fake select on enter
11919 							v = t.selectedValue;
11920 							t.selectedValue = null; // Needs to be null to fake change
11921 							Event.cancel(e);
11922 							t.settings.onselect(v);
11923 						}
11924 					});
11925 				}
11926 
11927 				t._focused = 1;
11928 			});
11929 			Event.add(t.id, 'blur', function() {
11930 				Event.remove(t.id, 'keydown', t.keyDownHandler);
11931 				Event.remove(t.id, 'keypress', t.keyPressHandler);
11932 				t._focused = 0;
11933 			});
11934 
11935 			// Old IE doesn't have hover on all elements
11936 			if (tinymce.isIE6 || !DOM.boxModel) {
11937 				Event.add(t.id, 'mouseover', function() {
11938 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11939 						DOM.addClass(t.id, cp + 'Hover');
11940 				});
11941 
11942 				Event.add(t.id, 'mouseout', function() {
11943 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11944 						DOM.removeClass(t.id, cp + 'Hover');
11945 				});
11946 			}
11947 
11948 			t.onPostRender.dispatch(t, DOM.get(t.id));
11949 		},
11950 
11951 		destroy : function() {
11952 			this.parent();
11953 
11954 			Event.clear(this.id + '_text');
11955 			Event.clear(this.id + '_open');
11956 		}
11957 	});
11958 })(tinymce);
11959 
11960 (function(tinymce) {
11961 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11962 
11963 	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
11964 		NativeListBox : function(id, s) {
11965 			this.parent(id, s);
11966 			this.classPrefix = 'mceNativeListBox';
11967 		},
11968 
11969 		setDisabled : function(s) {
11970 			DOM.get(this.id).disabled = s;
11971 			this.setAriaProperty('disabled', s);
11972 		},
11973 
11974 		isDisabled : function() {
11975 			return DOM.get(this.id).disabled;
11976 		},
11977 
11978 		select : function(va) {
11979 			var t = this, fv, f;
11980 
11981 			if (va == undef)
11982 				return t.selectByIndex(-1);
11983 
11984 			// Is string or number make function selector
11985 			if (va && typeof(va)=="function")
11986 				f = va;
11987 			else {
11988 				f = function(v) {
11989 					return v == va;
11990 				};
11991 			}
11992 
11993 			// Do we need to do something?
11994 			if (va != t.selectedValue) {
11995 				// Find item
11996 				each(t.items, function(o, i) {
11997 					if (f(o.value)) {
11998 						fv = 1;
11999 						t.selectByIndex(i);
12000 						return false;
12001 					}
12002 				});
12003 
12004 				if (!fv)
12005 					t.selectByIndex(-1);
12006 			}
12007 		},
12008 
12009 		selectByIndex : function(idx) {
12010 			DOM.get(this.id).selectedIndex = idx + 1;
12011 			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
12012 		},
12013 
12014 		add : function(n, v, a) {
12015 			var o, t = this;
12016 
12017 			a = a || {};
12018 			a.value = v;
12019 
12020 			if (t.isRendered())
12021 				DOM.add(DOM.get(this.id), 'option', a, n);
12022 
12023 			o = {
12024 				title : n,
12025 				value : v,
12026 				attribs : a
12027 			};
12028 
12029 			t.items.push(o);
12030 			t.onAdd.dispatch(t, o);
12031 		},
12032 
12033 		getLength : function() {
12034 			return this.items.length;
12035 		},
12036 
12037 		renderHTML : function() {
12038 			var h, t = this;
12039 
12040 			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
12041 
12042 			each(t.items, function(it) {
12043 				h += DOM.createHTML('option', {value : it.value}, it.title);
12044 			});
12045 
12046 			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
12047 			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
12048 			return h;
12049 		},
12050 
12051 		postRender : function() {
12052 			var t = this, ch, changeListenerAdded = true;
12053 
12054 			t.rendered = true;
12055 
12056 			function onChange(e) {
12057 				var v = t.items[e.target.selectedIndex - 1];
12058 
12059 				if (v && (v = v.value)) {
12060 					t.onChange.dispatch(t, v);
12061 
12062 					if (t.settings.onselect)
12063 						t.settings.onselect(v);
12064 				}
12065 			};
12066 
12067 			Event.add(t.id, 'change', onChange);
12068 
12069 			// Accessibility keyhandler
12070 			Event.add(t.id, 'keydown', function(e) {
12071 				var bf;
12072 
12073 				Event.remove(t.id, 'change', ch);
12074 				changeListenerAdded = false;
12075 
12076 				bf = Event.add(t.id, 'blur', function() {
12077 					if (changeListenerAdded) return;
12078 					changeListenerAdded = true;
12079 					Event.add(t.id, 'change', onChange);
12080 					Event.remove(t.id, 'blur', bf);
12081 				});
12082 
12083 				//prevent default left and right keys on chrome - so that the keyboard navigation is used.
12084 				if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
12085 					return Event.prevent(e);
12086 				}
12087 				
12088 				if (e.keyCode == 13 || e.keyCode == 32) {
12089 					onChange(e);
12090 					return Event.cancel(e);
12091 				}
12092 			});
12093 
12094 			t.onPostRender.dispatch(t, DOM.get(t.id));
12095 		}
12096 	});
12097 })(tinymce);
12098 
12099 (function(tinymce) {
12100 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12101 
12102 	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
12103 		MenuButton : function(id, s, ed) {
12104 			this.parent(id, s, ed);
12105 
12106 			this.onRenderMenu = new tinymce.util.Dispatcher(this);
12107 
12108 			s.menu_container = s.menu_container || DOM.doc.body;
12109 		},
12110 
12111 		showMenu : function() {
12112 			var t = this, p1, p2, e = DOM.get(t.id), m;
12113 
12114 			if (t.isDisabled())
12115 				return;
12116 
12117 			if (!t.isMenuRendered) {
12118 				t.renderMenu();
12119 				t.isMenuRendered = true;
12120 			}
12121 
12122 			if (t.isMenuVisible)
12123 				return t.hideMenu();
12124 
12125 			p1 = DOM.getPos(t.settings.menu_container);
12126 			p2 = DOM.getPos(e);
12127 
12128 			m = t.menu;
12129 			m.settings.offset_x = p2.x;
12130 			m.settings.offset_y = p2.y;
12131 			m.settings.vp_offset_x = p2.x;
12132 			m.settings.vp_offset_y = p2.y;
12133 			m.settings.keyboard_focus = t._focused;
12134 			m.showMenu(0, e.firstChild.clientHeight);
12135 
12136 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12137 			t.setState('Selected', 1);
12138 
12139 			t.isMenuVisible = 1;
12140 		},
12141 
12142 		renderMenu : function() {
12143 			var t = this, m;
12144 
12145 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12146 				menu_line : 1,
12147 				'class' : this.classPrefix + 'Menu',
12148 				icons : t.settings.icons
12149 			});
12150 
12151 			m.onHideMenu.add(function() {
12152 				t.hideMenu();
12153 				t.focus();
12154 			});
12155 
12156 			t.onRenderMenu.dispatch(t, m);
12157 			t.menu = m;
12158 		},
12159 
12160 		hideMenu : function(e) {
12161 			var t = this;
12162 
12163 			// Prevent double toogles by canceling the mouse click event to the button
12164 			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
12165 				return;
12166 
12167 			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12168 				t.setState('Selected', 0);
12169 				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12170 				if (t.menu)
12171 					t.menu.hideMenu();
12172 			}
12173 
12174 			t.isMenuVisible = 0;
12175 		},
12176 
12177 		postRender : function() {
12178 			var t = this, s = t.settings;
12179 
12180 			Event.add(t.id, 'click', function() {
12181 				if (!t.isDisabled()) {
12182 					if (s.onclick)
12183 						s.onclick(t.value);
12184 
12185 					t.showMenu();
12186 				}
12187 			});
12188 		}
12189 	});
12190 })(tinymce);
12191 
12192 (function(tinymce) {
12193 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12194 
12195 	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
12196 		SplitButton : function(id, s, ed) {
12197 			this.parent(id, s, ed);
12198 			this.classPrefix = 'mceSplitButton';
12199 		},
12200 
12201 		renderHTML : function() {
12202 			var h, t = this, s = t.settings, h1;
12203 
12204 			h = '<tbody><tr>';
12205 
12206 			if (s.image)
12207 				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
12208 			else
12209 				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
12210 
12211 			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
12212 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12213 	
12214 			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
12215 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12216 
12217 			h += '</tr></tbody>';
12218 			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
12219 			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
12220 		},
12221 
12222 		postRender : function() {
12223 			var t = this, s = t.settings, activate;
12224 
12225 			if (s.onclick) {
12226 				activate = function(evt) {
12227 					if (!t.isDisabled()) {
12228 						s.onclick(t.value);
12229 						Event.cancel(evt);
12230 					}
12231 				};
12232 				Event.add(t.id + '_action', 'click', activate);
12233 				Event.add(t.id, ['click', 'keydown'], function(evt) {
12234 					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
12235 					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
12236 						activate();
12237 						Event.cancel(evt);
12238 					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
12239 						t.showMenu();
12240 						Event.cancel(evt);
12241 					}
12242 				});
12243 			}
12244 
12245 			Event.add(t.id + '_open', 'click', function (evt) {
12246 				t.showMenu();
12247 				Event.cancel(evt);
12248 			});
12249 			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
12250 			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
12251 
12252 			// Old IE doesn't have hover on all elements
12253 			if (tinymce.isIE6 || !DOM.boxModel) {
12254 				Event.add(t.id, 'mouseover', function() {
12255 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12256 						DOM.addClass(t.id, 'mceSplitButtonHover');
12257 				});
12258 
12259 				Event.add(t.id, 'mouseout', function() {
12260 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12261 						DOM.removeClass(t.id, 'mceSplitButtonHover');
12262 				});
12263 			}
12264 		},
12265 
12266 		destroy : function() {
12267 			this.parent();
12268 
12269 			Event.clear(this.id + '_action');
12270 			Event.clear(this.id + '_open');
12271 			Event.clear(this.id);
12272 		}
12273 	});
12274 })(tinymce);
12275 
12276 (function(tinymce) {
12277 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
12278 
12279 	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
12280 		ColorSplitButton : function(id, s, ed) {
12281 			var t = this;
12282 
12283 			t.parent(id, s, ed);
12284 
12285 			t.settings = s = tinymce.extend({
12286 				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
12287 				grid_width : 8,
12288 				default_color : '#888888'
12289 			}, t.settings);
12290 
12291 			t.onShowMenu = new tinymce.util.Dispatcher(t);
12292 
12293 			t.onHideMenu = new tinymce.util.Dispatcher(t);
12294 
12295 			t.value = s.default_color;
12296 		},
12297 
12298 		showMenu : function() {
12299 			var t = this, r, p, e, p2;
12300 
12301 			if (t.isDisabled())
12302 				return;
12303 
12304 			if (!t.isMenuRendered) {
12305 				t.renderMenu();
12306 				t.isMenuRendered = true;
12307 			}
12308 
12309 			if (t.isMenuVisible)
12310 				return t.hideMenu();
12311 
12312 			e = DOM.get(t.id);
12313 			DOM.show(t.id + '_menu');
12314 			DOM.addClass(e, 'mceSplitButtonSelected');
12315 			p2 = DOM.getPos(e);
12316 			DOM.setStyles(t.id + '_menu', {
12317 				left : p2.x,
12318 				top : p2.y + e.firstChild.clientHeight,
12319 				zIndex : 200000
12320 			});
12321 			e = 0;
12322 
12323 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12324 			t.onShowMenu.dispatch(t);
12325 
12326 			if (t._focused) {
12327 				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
12328 					if (e.keyCode == 27)
12329 						t.hideMenu();
12330 				});
12331 
12332 				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
12333 			}
12334 
12335 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
12336 				root: t.id + '_menu',
12337 				items: DOM.select('a', t.id + '_menu'),
12338 				onCancel: function() {
12339 					t.hideMenu();
12340 					t.focus();
12341 				}
12342 			});
12343 
12344 			t.keyboardNav.focus();
12345 			t.isMenuVisible = 1;
12346 		},
12347 
12348 		hideMenu : function(e) {
12349 			var t = this;
12350 
12351 			if (t.isMenuVisible) {
12352 				// Prevent double toogles by canceling the mouse click event to the button
12353 				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
12354 					return;
12355 
12356 				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
12357 					DOM.removeClass(t.id, 'mceSplitButtonSelected');
12358 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12359 					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
12360 					DOM.hide(t.id + '_menu');
12361 				}
12362 
12363 				t.isMenuVisible = 0;
12364 				t.onHideMenu.dispatch();
12365 				t.keyboardNav.destroy();
12366 			}
12367 		},
12368 
12369 		renderMenu : function() {
12370 			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
12371 
12372 			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
12373 			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
12374 			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
12375 
12376 			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
12377 			tb = DOM.add(n, 'tbody');
12378 
12379 			// Generate color grid
12380 			i = 0;
12381 			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
12382 				c = c.replace(/^#/, '');
12383 
12384 				if (!i--) {
12385 					tr = DOM.add(tb, 'tr');
12386 					i = s.grid_width - 1;
12387 				}
12388 
12389 				n = DOM.add(tr, 'td');
12390 				var settings = {
12391 					href : 'javascript:;',
12392 					style : {
12393 						backgroundColor : '#' + c
12394 					},
12395 					'title': t.editor.getLang('colors.' + c, c),
12396 					'data-mce-color' : '#' + c
12397 				};
12398 
12399 				// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
12400 				if (!tinymce.isIE ) {
12401 					settings.role = 'option';
12402 				}
12403 
12404 				n = DOM.add(n, 'a', settings);
12405 
12406 				if (t.editor.forcedHighContrastMode) {
12407 					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
12408 					if (n.getContext && (context = n.getContext("2d"))) {
12409 						context.fillStyle = '#' + c;
12410 						context.fillRect(0, 0, 16, 16);
12411 					} else {
12412 						// No point leaving a canvas element around if it's not supported for drawing on anyway.
12413 						DOM.remove(n);
12414 					}
12415 				}
12416 			});
12417 
12418 			if (s.more_colors_func) {
12419 				n = DOM.add(tb, 'tr');
12420 				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
12421 				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
12422 
12423 				Event.add(n, 'click', function(e) {
12424 					s.more_colors_func.call(s.more_colors_scope || this);
12425 					return Event.cancel(e); // Cancel to fix onbeforeunload problem
12426 				});
12427 			}
12428 
12429 			DOM.addClass(m, 'mceColorSplitMenu');
12430 
12431 			// Prevent IE from scrolling and hindering click to occur #4019
12432 			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
12433 
12434 			Event.add(t.id + '_menu', 'click', function(e) {
12435 				var c;
12436 
12437 				e = DOM.getParent(e.target, 'a', tb);
12438 
12439 				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
12440 					t.setColor(c);
12441 
12442 				return false; // Prevent IE auto save warning
12443 			});
12444 
12445 			return w;
12446 		},
12447 
12448 		setColor : function(c) {
12449 			this.displayColor(c);
12450 			this.hideMenu();
12451 			this.settings.onselect(c);
12452 		},
12453 		
12454 		displayColor : function(c) {
12455 			var t = this;
12456 
12457 			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
12458 
12459 			t.value = c;
12460 		},
12461 
12462 		postRender : function() {
12463 			var t = this, id = t.id;
12464 
12465 			t.parent();
12466 			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
12467 			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
12468 		},
12469 
12470 		destroy : function() {
12471 			var self = this;
12472 
12473 			self.parent();
12474 
12475 			Event.clear(self.id + '_menu');
12476 			Event.clear(self.id + '_more');
12477 			DOM.remove(self.id + '_menu');
12478 
12479 			if (self.keyboardNav) {
12480 				self.keyboardNav.destroy();
12481 			}
12482 		}
12483 	});
12484 })(tinymce);
12485 
12486 (function(tinymce) {
12487 // Shorten class names
12488 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
12489 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
12490 	renderHTML : function() {
12491 		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
12492 
12493 		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
12494 		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
12495 		h.push("<span role='application'>");
12496 		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
12497 		each(controls, function(toolbar) {
12498 			h.push(toolbar.renderHTML());
12499 		});
12500 		h.push("</span>");
12501 		h.push('</div>');
12502 
12503 		return h.join('');
12504 	},
12505 	
12506 	focus : function() {
12507 		var t = this;
12508 		dom.get(t.id).focus();
12509 	},
12510 	
12511 	postRender : function() {
12512 		var t = this, items = [];
12513 
12514 		each(t.controls, function(toolbar) {
12515 			each (toolbar.controls, function(control) {
12516 				if (control.id) {
12517 					items.push(control);
12518 				}
12519 			});
12520 		});
12521 
12522 		t.keyNav = new tinymce.ui.KeyboardNavigation({
12523 			root: t.id,
12524 			items: items,
12525 			onCancel: function() {
12526 				//Move focus if webkit so that navigation back will read the item.
12527 				if (tinymce.isWebKit) {
12528 					dom.get(t.editor.id+"_ifr").focus();
12529 				}
12530 				t.editor.focus();
12531 			},
12532 			excludeFromTabOrder: !t.settings.tab_focus_toolbar
12533 		});
12534 	},
12535 	
12536 	destroy : function() {
12537 		var self = this;
12538 
12539 		self.parent();
12540 		self.keyNav.destroy();
12541 		Event.clear(self.id);
12542 	}
12543 });
12544 })(tinymce);
12545 
12546 (function(tinymce) {
12547 // Shorten class names
12548 var dom = tinymce.DOM, each = tinymce.each;
12549 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12550 	renderHTML : function() {
12551 		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
12552 
12553 		cl = t.controls;
12554 		for (i=0; i<cl.length; i++) {
12555 			// Get current control, prev control, next control and if the control is a list box or not
12556 			co = cl[i];
12557 			pr = cl[i - 1];
12558 			nx = cl[i + 1];
12559 
12560 			// Add toolbar start
12561 			if (i === 0) {
12562 				c = 'mceToolbarStart';
12563 
12564 				if (co.Button)
12565 					c += ' mceToolbarStartButton';
12566 				else if (co.SplitButton)
12567 					c += ' mceToolbarStartSplitButton';
12568 				else if (co.ListBox)
12569 					c += ' mceToolbarStartListBox';
12570 
12571 				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12572 			}
12573 
12574 			// Add toolbar end before list box and after the previous button
12575 			// This is to fix the o2k7 editor skins
12576 			if (pr && co.ListBox) {
12577 				if (pr.Button || pr.SplitButton)
12578 					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
12579 			}
12580 
12581 			// Render control HTML
12582 
12583 			// IE 8 quick fix, needed to propertly generate a hit area for anchors
12584 			if (dom.stdMode)
12585 				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
12586 			else
12587 				h += '<td>' + co.renderHTML() + '</td>';
12588 
12589 			// Add toolbar start after list box and before the next button
12590 			// This is to fix the o2k7 editor skins
12591 			if (nx && co.ListBox) {
12592 				if (nx.Button || nx.SplitButton)
12593 					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
12594 			}
12595 		}
12596 
12597 		c = 'mceToolbarEnd';
12598 
12599 		if (co.Button)
12600 			c += ' mceToolbarEndButton';
12601 		else if (co.SplitButton)
12602 			c += ' mceToolbarEndSplitButton';
12603 		else if (co.ListBox)
12604 			c += ' mceToolbarEndListBox';
12605 
12606 		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12607 
12608 		return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
12609 	}
12610 });
12611 })(tinymce);
12612 
12613 (function(tinymce) {
12614 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
12615 
12616 	tinymce.create('tinymce.AddOnManager', {
12617 		AddOnManager : function() {
12618 			var self = this;
12619 
12620 			self.items = [];
12621 			self.urls = {};
12622 			self.lookup = {};
12623 			self.onAdd = new Dispatcher(self);
12624 		},
12625 
12626 		get : function(n) {
12627 			if (this.lookup[n]) {
12628 				return this.lookup[n].instance;
12629 			} else {
12630 				return undefined;
12631 			}
12632 		},
12633 
12634 		dependencies : function(n) {
12635 			var result;
12636 			if (this.lookup[n]) {
12637 				result = this.lookup[n].dependencies;
12638 			}
12639 			return result || [];
12640 		},
12641 
12642 		requireLangPack : function(n) {
12643 			var s = tinymce.settings;
12644 
12645 			if (s && s.language && s.language_load !== false)
12646 				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
12647 		},
12648 
12649 		add : function(id, o, dependencies) {
12650 			this.items.push(o);
12651 			this.lookup[id] = {instance:o, dependencies:dependencies};
12652 			this.onAdd.dispatch(this, id, o);
12653 
12654 			return o;
12655 		},
12656 		createUrl: function(baseUrl, dep) {
12657 			if (typeof dep === "object") {
12658 				return dep
12659 			} else {
12660 				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
12661 			}
12662 		},
12663 
12664 		addComponents: function(pluginName, scripts) {
12665 			var pluginUrl = this.urls[pluginName];
12666 			tinymce.each(scripts, function(script){
12667 				tinymce.ScriptLoader.add(pluginUrl+"/"+script);	
12668 			});
12669 		},
12670 
12671 		load : function(n, u, cb, s) {
12672 			var t = this, url = u;
12673 
12674 			function loadDependencies() {
12675 				var dependencies = t.dependencies(n);
12676 				tinymce.each(dependencies, function(dep) {
12677 					var newUrl = t.createUrl(u, dep);
12678 					t.load(newUrl.resource, newUrl, undefined, undefined);
12679 				});
12680 				if (cb) {
12681 					if (s) {
12682 						cb.call(s);
12683 					} else {
12684 						cb.call(tinymce.ScriptLoader);
12685 					}
12686 				}
12687 			}
12688 
12689 			if (t.urls[n])
12690 				return;
12691 			if (typeof u === "object")
12692 				url = u.prefix + u.resource + u.suffix;
12693 
12694 			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
12695 				url = tinymce.baseURL + '/' + url;
12696 
12697 			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
12698 
12699 			if (t.lookup[n]) {
12700 				loadDependencies();
12701 			} else {
12702 				tinymce.ScriptLoader.add(url, loadDependencies, s);
12703 			}
12704 		}
12705 	});
12706 
12707 	// Create plugin and theme managers
12708 	tinymce.PluginManager = new tinymce.AddOnManager();
12709 	tinymce.ThemeManager = new tinymce.AddOnManager();
12710 }(tinymce));
12711 
12712 (function(tinymce) {
12713 	// Shorten names
12714 	var each = tinymce.each, extend = tinymce.extend,
12715 		DOM = tinymce.DOM, Event = tinymce.dom.Event,
12716 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
12717 		explode = tinymce.explode,
12718 		Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
12719 
12720 	// Setup some URLs where the editor API is located and where the document is
12721 	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
12722 	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
12723 		tinymce.documentBaseURL += '/';
12724 
12725 	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
12726 
12727 	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
12728 
12729 	// Add before unload listener
12730 	// This was required since IE was leaking memory if you added and removed beforeunload listeners
12731 	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
12732 	tinymce.onBeforeUnload = new Dispatcher(tinymce);
12733 
12734 	// Must be on window or IE will leak if the editor is placed in frame or iframe
12735 	Event.add(window, 'beforeunload', function(e) {
12736 		tinymce.onBeforeUnload.dispatch(tinymce, e);
12737 	});
12738 
12739 	tinymce.onAddEditor = new Dispatcher(tinymce);
12740 
12741 	tinymce.onRemoveEditor = new Dispatcher(tinymce);
12742 
12743 	tinymce.EditorManager = extend(tinymce, {
12744 		editors : [],
12745 
12746 		i18n : {},
12747 
12748 		activeEditor : null,
12749 
12750 		init : function(s) {
12751 			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
12752 
12753 			function createId(elm) {
12754 				var id = elm.id;
12755 	
12756 				// Use element id, or unique name or generate a unique id
12757 				if (!id) {
12758 					id = elm.name;
12759 	
12760 					if (id && !DOM.get(id)) {
12761 						id = elm.name;
12762 					} else {
12763 						// Generate unique name
12764 						id = DOM.uniqueId();
12765 					}
12766 
12767 					elm.setAttribute('id', id);
12768 				}
12769 
12770 				return id;
12771 			};
12772 
12773 			function execCallback(se, n, s) {
12774 				var f = se[n];
12775 
12776 				if (!f)
12777 					return;
12778 
12779 				if (tinymce.is(f, 'string')) {
12780 					s = f.replace(/\.\w+$/, '');
12781 					s = s ? tinymce.resolve(s) : 0;
12782 					f = tinymce.resolve(f);
12783 				}
12784 
12785 				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
12786 			};
12787 
12788 			function hasClass(n, c) {
12789 				return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
12790 			};
12791 
12792 			t.settings = s;
12793 
12794 			// Legacy call
12795 			Event.bind(window, 'ready', function() {
12796 				var l, co;
12797 
12798 				execCallback(s, 'onpageload');
12799 
12800 				switch (s.mode) {
12801 					case "exact":
12802 						l = s.elements || '';
12803 
12804 						if(l.length > 0) {
12805 							each(explode(l), function(v) {
12806 								if (DOM.get(v)) {
12807 									ed = new tinymce.Editor(v, s);
12808 									el.push(ed);
12809 									ed.render(1);
12810 								} else {
12811 									each(document.forms, function(f) {
12812 										each(f.elements, function(e) {
12813 											if (e.name === v) {
12814 												v = 'mce_editor_' + instanceCounter++;
12815 												DOM.setAttrib(e, 'id', v);
12816 
12817 												ed = new tinymce.Editor(v, s);
12818 												el.push(ed);
12819 												ed.render(1);
12820 											}
12821 										});
12822 									});
12823 								}
12824 							});
12825 						}
12826 						break;
12827 
12828 					case "textareas":
12829 					case "specific_textareas":
12830 						each(DOM.select('textarea'), function(elm) {
12831 							if (s.editor_deselector && hasClass(elm, s.editor_deselector))
12832 								return;
12833 
12834 							if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
12835 								ed = new tinymce.Editor(createId(elm), s);
12836 								el.push(ed);
12837 								ed.render(1);
12838 							}
12839 						});
12840 						break;
12841 					
12842 					default:
12843 						if (s.types) {
12844 							// Process type specific selector
12845 							each(s.types, function(type) {
12846 								each(DOM.select(type.selector), function(elm) {
12847 									var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
12848 									el.push(editor);
12849 									editor.render(1);
12850 								});
12851 							});
12852 						} else if (s.selector) {
12853 							// Process global selector
12854 							each(DOM.select(s.selector), function(elm) {
12855 								var editor = new tinymce.Editor(createId(elm), s);
12856 								el.push(editor);
12857 								editor.render(1);
12858 							});
12859 						}
12860 				}
12861 
12862 				// Call onInit when all editors are initialized
12863 				if (s.oninit) {
12864 					l = co = 0;
12865 
12866 					each(el, function(ed) {
12867 						co++;
12868 
12869 						if (!ed.initialized) {
12870 							// Wait for it
12871 							ed.onInit.add(function() {
12872 								l++;
12873 
12874 								// All done
12875 								if (l == co)
12876 									execCallback(s, 'oninit');
12877 							});
12878 						} else
12879 							l++;
12880 
12881 						// All done
12882 						if (l == co)
12883 							execCallback(s, 'oninit');					
12884 					});
12885 				}
12886 			});
12887 		},
12888 
12889 		get : function(id) {
12890 			if (id === undef)
12891 				return this.editors;
12892 
12893 			return this.editors[id];
12894 		},
12895 
12896 		getInstanceById : function(id) {
12897 			return this.get(id);
12898 		},
12899 
12900 		add : function(editor) {
12901 			var self = this, editors = self.editors;
12902 
12903 			// Add named and index editor instance
12904 			editors[editor.id] = editor;
12905 			editors.push(editor);
12906 
12907 			self._setActive(editor);
12908 			self.onAddEditor.dispatch(self, editor);
12909 
12910 
12911 			return editor;
12912 		},
12913 
12914 		remove : function(editor) {
12915 			var t = this, i, editors = t.editors;
12916 
12917 			// Not in the collection
12918 			if (!editors[editor.id])
12919 				return null;
12920 
12921 			delete editors[editor.id];
12922 
12923 			for (i = 0; i < editors.length; i++) {
12924 				if (editors[i] == editor) {
12925 					editors.splice(i, 1);
12926 					break;
12927 				}
12928 			}
12929 
12930 			// Select another editor since the active one was removed
12931 			if (t.activeEditor == editor)
12932 				t._setActive(editors[0]);
12933 
12934 			editor.destroy();
12935 			t.onRemoveEditor.dispatch(t, editor);
12936 
12937 			return editor;
12938 		},
12939 
12940 		execCommand : function(c, u, v) {
12941 			var t = this, ed = t.get(v), w;
12942 
12943 			function clr() {
12944 				ed.destroy();
12945 				w.detachEvent('onunload', clr);
12946 				w = w.tinyMCE = w.tinymce = null; // IE leak
12947 			};
12948 
12949 			// Manager commands
12950 			switch (c) {
12951 				case "mceFocus":
12952 					ed.focus();
12953 					return true;
12954 
12955 				case "mceAddEditor":
12956 				case "mceAddControl":
12957 					if (!t.get(v))
12958 						new tinymce.Editor(v, t.settings).render();
12959 
12960 					return true;
12961 
12962 				case "mceAddFrameControl":
12963 					w = v.window;
12964 
12965 					// Add tinyMCE global instance and tinymce namespace to specified window
12966 					w.tinyMCE = tinyMCE;
12967 					w.tinymce = tinymce;
12968 
12969 					tinymce.DOM.doc = w.document;
12970 					tinymce.DOM.win = w;
12971 
12972 					ed = new tinymce.Editor(v.element_id, v);
12973 					ed.render();
12974 
12975 					// Fix IE memory leaks
12976 					if (tinymce.isIE) {
12977 						w.attachEvent('onunload', clr);
12978 					}
12979 
12980 					v.page_window = null;
12981 
12982 					return true;
12983 
12984 				case "mceRemoveEditor":
12985 				case "mceRemoveControl":
12986 					if (ed)
12987 						ed.remove();
12988 
12989 					return true;
12990 
12991 				case 'mceToggleEditor':
12992 					if (!ed) {
12993 						t.execCommand('mceAddControl', 0, v);
12994 						return true;
12995 					}
12996 
12997 					if (ed.isHidden())
12998 						ed.show();
12999 					else
13000 						ed.hide();
13001 
13002 					return true;
13003 			}
13004 
13005 			// Run command on active editor
13006 			if (t.activeEditor)
13007 				return t.activeEditor.execCommand(c, u, v);
13008 
13009 			return false;
13010 		},
13011 
13012 		execInstanceCommand : function(id, c, u, v) {
13013 			var ed = this.get(id);
13014 
13015 			if (ed)
13016 				return ed.execCommand(c, u, v);
13017 
13018 			return false;
13019 		},
13020 
13021 		triggerSave : function() {
13022 			each(this.editors, function(e) {
13023 				e.save();
13024 			});
13025 		},
13026 
13027 		addI18n : function(p, o) {
13028 			var lo, i18n = this.i18n;
13029 
13030 			if (!tinymce.is(p, 'string')) {
13031 				each(p, function(o, lc) {
13032 					each(o, function(o, g) {
13033 						each(o, function(o, k) {
13034 							if (g === 'common')
13035 								i18n[lc + '.' + k] = o;
13036 							else
13037 								i18n[lc + '.' + g + '.' + k] = o;
13038 						});
13039 					});
13040 				});
13041 			} else {
13042 				each(o, function(o, k) {
13043 					i18n[p + '.' + k] = o;
13044 				});
13045 			}
13046 		},
13047 
13048 		// Private methods
13049 
13050 		_setActive : function(editor) {
13051 			this.selectedInstance = this.activeEditor = editor;
13052 		}
13053 	});
13054 })(tinymce);
13055 
13056 (function(tinymce) {
13057 	// Shorten these names
13058 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
13059 		each = tinymce.each, isGecko = tinymce.isGecko,
13060 		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
13061 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13062 		explode = tinymce.explode;
13063 
13064 	tinymce.create('tinymce.Editor', {
13065 		Editor : function(id, settings) {
13066 			var self = this, TRUE = true;
13067 
13068 			self.settings = settings = extend({
13069 				id : id,
13070 				language : 'en',
13071 				theme : 'advanced',
13072 				skin : 'default',
13073 				delta_width : 0,
13074 				delta_height : 0,
13075 				popup_css : '',
13076 				plugins : '',
13077 				document_base_url : tinymce.documentBaseURL,
13078 				add_form_submit_trigger : TRUE,
13079 				submit_patch : TRUE,
13080 				add_unload_trigger : TRUE,
13081 				convert_urls : TRUE,
13082 				relative_urls : TRUE,
13083 				remove_script_host : TRUE,
13084 				table_inline_editing : false,
13085 				object_resizing : TRUE,
13086 				accessibility_focus : TRUE,
13087 				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
13088 				visual : TRUE,
13089 				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
13090 				font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
13091 				apply_source_formatting : TRUE,
13092 				directionality : 'ltr',
13093 				forced_root_block : 'p',
13094 				hidden_input : TRUE,
13095 				padd_empty_editor : TRUE,
13096 				render_ui : TRUE,
13097 				indentation : '30px',
13098 				fix_table_elements : TRUE,
13099 				inline_styles : TRUE,
13100 				convert_fonts_to_spans : TRUE,
13101 				indent : 'simple',
13102 				indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13103 				indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13104 				validate : TRUE,
13105 				entity_encoding : 'named',
13106 				url_converter : self.convertURL,
13107 				url_converter_scope : self,
13108 				ie7_compat : TRUE
13109 			}, settings);
13110 
13111 			self.id = self.editorId = id;
13112 
13113 			self.isNotDirty = false;
13114 
13115 			self.plugins = {};
13116 
13117 			self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
13118 				base_uri : tinyMCE.baseURI
13119 			});
13120 
13121 			self.baseURI = tinymce.baseURI;
13122 
13123 			self.contentCSS = [];
13124 
13125 			self.contentStyles = [];
13126 
13127 			// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
13128 			self.setupEvents();
13129 
13130 			// Internal command handler objects
13131 			self.execCommands = {};
13132 			self.queryStateCommands = {};
13133 			self.queryValueCommands = {};
13134 
13135 			// Call setup
13136 			self.execCallback('setup', self);
13137 		},
13138 
13139 		render : function(nst) {
13140 			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
13141 
13142 			// Page is not loaded yet, wait for it
13143 			if (!Event.domLoaded) {
13144 				Event.add(window, 'ready', function() {
13145 					t.render();
13146 				});
13147 				return;
13148 			}
13149 
13150 			tinyMCE.settings = s;
13151 
13152 			// Element not found, then skip initialization
13153 			if (!t.getElement())
13154 				return;
13155 
13156 			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
13157 			// here since the browser says it has contentEditable support but there is no visible caret.
13158 			if (tinymce.isIDevice && !tinymce.isIOS5)
13159 				return;
13160 
13161 			// Add hidden input for non input elements inside form elements
13162 			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
13163 				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
13164 
13165 			// Hide target element early to prevent content flashing
13166 			if (!s.content_editable) {
13167 				t.orgVisibility = t.getElement().style.visibility;
13168 				t.getElement().style.visibility = 'hidden';
13169 			}
13170 
13171 			if (tinymce.WindowManager)
13172 				t.windowManager = new tinymce.WindowManager(t);
13173 
13174 			if (s.encoding == 'xml') {
13175 				t.onGetContent.add(function(ed, o) {
13176 					if (o.save)
13177 						o.content = DOM.encode(o.content);
13178 				});
13179 			}
13180 
13181 			if (s.add_form_submit_trigger) {
13182 				t.onSubmit.addToTop(function() {
13183 					if (t.initialized) {
13184 						t.save();
13185 						t.isNotDirty = 1;
13186 					}
13187 				});
13188 			}
13189 
13190 			if (s.add_unload_trigger) {
13191 				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
13192 					if (t.initialized && !t.destroyed && !t.isHidden())
13193 						t.save({format : 'raw', no_events : true});
13194 				});
13195 			}
13196 
13197 			tinymce.addUnload(t.destroy, t);
13198 
13199 			if (s.submit_patch) {
13200 				t.onBeforeRenderUI.add(function() {
13201 					var n = t.getElement().form;
13202 
13203 					if (!n)
13204 						return;
13205 
13206 					// Already patched
13207 					if (n._mceOldSubmit)
13208 						return;
13209 
13210 					// Check page uses id="submit" or name="submit" for it's submit button
13211 					if (!n.submit.nodeType && !n.submit.length) {
13212 						t.formElement = n;
13213 						n._mceOldSubmit = n.submit;
13214 						n.submit = function() {
13215 							// Save all instances
13216 							tinymce.triggerSave();
13217 							t.isNotDirty = 1;
13218 
13219 							return t.formElement._mceOldSubmit(t.formElement);
13220 						};
13221 					}
13222 
13223 					n = null;
13224 				});
13225 			}
13226 
13227 			// Load scripts
13228 			function loadScripts() {
13229 				if (s.language && s.language_load !== false)
13230 					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
13231 
13232 				if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
13233 					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
13234 
13235 				each(explode(s.plugins), function(p) {
13236 					if (p &&!PluginManager.urls[p]) {
13237 						if (p.charAt(0) == '-') {
13238 							p = p.substr(1, p.length);
13239 							var dependencies = PluginManager.dependencies(p);
13240 							each(dependencies, function(dep) {
13241 								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
13242 								dep = PluginManager.createUrl(defaultSettings, dep);
13243 								PluginManager.load(dep.resource, dep);
13244 							});
13245 						} else {
13246 							// Skip safari plugin, since it is removed as of 3.3b1
13247 							if (p == 'safari') {
13248 								return;
13249 							}
13250 							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
13251 						}
13252 					}
13253 				});
13254 
13255 				// Init when que is loaded
13256 				sl.loadQueue(function() {
13257 					if (!t.removed)
13258 						t.init();
13259 				});
13260 			};
13261 
13262 			loadScripts();
13263 		},
13264 
13265 		init : function() {
13266 			var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
13267 
13268 			tinymce.add(t);
13269 
13270 			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
13271 
13272 			if (s.theme) {
13273 				if (typeof s.theme != "function") {
13274 					s.theme = s.theme.replace(/-/, '');
13275 					o = ThemeManager.get(s.theme);
13276 					t.theme = new o();
13277 
13278 					if (t.theme.init)
13279 						t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
13280 				} else {
13281 					t.theme = s.theme;
13282 				}
13283 			}
13284 
13285 			function initPlugin(p) {
13286 				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
13287 				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
13288 					each(PluginManager.dependencies(p), function(dep){
13289 						initPlugin(dep);
13290 					});
13291 					po = new c(t, u);
13292 
13293 					t.plugins[p] = po;
13294 
13295 					if (po.init) {
13296 						po.init(t, u);
13297 						initializedPlugins.push(p);
13298 					}
13299 				}
13300 			}
13301 			
13302 			// Create all plugins
13303 			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
13304 
13305 			// Setup popup CSS path(s)
13306 			if (s.popup_css !== false) {
13307 				if (s.popup_css)
13308 					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
13309 				else
13310 					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
13311 			}
13312 
13313 			if (s.popup_css_add)
13314 				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
13315 
13316 			t.controlManager = new tinymce.ControlManager(t);
13317 
13318 			// Enables users to override the control factory
13319 			t.onBeforeRenderUI.dispatch(t, t.controlManager);
13320 
13321 			// Measure box
13322 			if (s.render_ui && t.theme) {
13323 				t.orgDisplay = e.style.display;
13324 
13325 				if (typeof s.theme != "function") {
13326 					w = s.width || e.style.width || e.offsetWidth;
13327 					h = s.height || e.style.height || e.offsetHeight;
13328 					mh = s.min_height || 100;
13329 					re = /^[0-9\.]+(|px)$/i;
13330 
13331 					if (re.test('' + w))
13332 						w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
13333 
13334 					if (re.test('' + h))
13335 						h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
13336 
13337 					// Render UI
13338 					o = t.theme.renderUI({
13339 						targetNode : e,
13340 						width : w,
13341 						height : h,
13342 						deltaWidth : s.delta_width,
13343 						deltaHeight : s.delta_height
13344 					});
13345 
13346 					// Resize editor
13347 					DOM.setStyles(o.sizeContainer || o.editorContainer, {
13348 						width : w,
13349 						height : h
13350 					});
13351 
13352 					h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
13353 					if (h < mh)
13354 						h = mh;
13355 				} else {
13356 					o = s.theme(t, e);
13357 
13358 					// Convert element type to id:s
13359 					if (o.editorContainer.nodeType) {
13360 						o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
13361 					}
13362 
13363 					// Convert element type to id:s
13364 					if (o.iframeContainer.nodeType) {
13365 						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
13366 					}
13367 
13368 					// Use specified iframe height or the targets offsetHeight
13369 					h = o.iframeHeight || e.offsetHeight;
13370 
13371 					// Store away the selection when it's changed to it can be restored later with a editor.focus() call
13372 					if (isIE) {
13373 						t.onInit.add(function(ed) {
13374 							ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
13375 								ed.lastIERng = ed.selection.getRng();
13376 							});
13377 						});
13378 					}
13379 				}
13380 
13381 				t.editorContainer = o.editorContainer;
13382 			}
13383 
13384 			// Load specified content CSS last
13385 			if (s.content_css) {
13386 				each(explode(s.content_css), function(u) {
13387 					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
13388 				});
13389 			}
13390 
13391 			// Content editable mode ends here
13392 			if (s.content_editable) {
13393 				e = n = o = null; // Fix IE leak
13394 				return t.initContentBody();
13395 			}
13396 
13397 			// User specified a document.domain value
13398 			if (document.domain && location.hostname != document.domain)
13399 				tinymce.relaxedDomain = document.domain;
13400 
13401 			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
13402 
13403 			// We only need to override paths if we have to
13404 			// IE has a bug where it remove site absolute urls to relative ones if this is specified
13405 			if (s.document_base_url != tinymce.documentBaseURL)
13406 				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
13407 
13408 			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
13409 			if (s.ie7_compat)
13410 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
13411 			else
13412 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
13413 
13414 			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
13415 
13416 			// Load the CSS by injecting them into the HTML this will reduce "flicker"
13417 			for (i = 0; i < t.contentCSS.length; i++) {
13418 				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
13419 			}
13420 
13421 			t.contentCSS = [];
13422 
13423 			bi = s.body_id || 'tinymce';
13424 			if (bi.indexOf('=') != -1) {
13425 				bi = t.getParam('body_id', '', 'hash');
13426 				bi = bi[t.id] || bi;
13427 			}
13428 
13429 			bc = s.body_class || '';
13430 			if (bc.indexOf('=') != -1) {
13431 				bc = t.getParam('body_class', '', 'hash');
13432 				bc = bc[t.id] || '';
13433 			}
13434 
13435 			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
13436 
13437 			// Domain relaxing enabled, then set document domain
13438 			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
13439 				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
13440 				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
13441 			}
13442 
13443 			// Create iframe
13444 			// TODO: ACC add the appropriate description on this.
13445 			n = DOM.add(o.iframeContainer, 'iframe', { 
13446 				id : t.id + "_ifr",
13447 				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
13448 				frameBorder : '0',
13449 				allowTransparency : "true",
13450 				title : s.aria_label,
13451 				style : {
13452 					width : '100%',
13453 					height : h,
13454 					display : 'block' // Important for Gecko to render the iframe correctly
13455 				}
13456 			});
13457 
13458 			t.contentAreaContainer = o.iframeContainer;
13459 
13460 			if (o.editorContainer) {
13461 				DOM.get(o.editorContainer).style.display = t.orgDisplay;
13462 			}
13463 
13464 			// Restore visibility on target element
13465 			e.style.visibility = t.orgVisibility;
13466 
13467 			DOM.get(t.id).style.display = 'none';
13468 			DOM.setAttrib(t.id, 'aria-hidden', true);
13469 
13470 			if (!tinymce.relaxedDomain || !u)
13471 				t.initContentBody();
13472 
13473 			e = n = o = null; // Cleanup
13474 		},
13475 
13476 		initContentBody : function() {
13477 			var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
13478 
13479 			// Setup iframe body
13480 			if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
13481 				doc.open();
13482 				doc.write(self.iframeHTML);
13483 				doc.close();
13484 
13485 				if (tinymce.relaxedDomain)
13486 					doc.domain = tinymce.relaxedDomain;
13487 			}
13488 
13489 			if (settings.content_editable) {
13490 				DOM.addClass(targetElm, 'mceContentBody');
13491 				self.contentDocument = doc = settings.content_document || document;
13492 				self.contentWindow = settings.content_window || window;
13493 				self.bodyElement = targetElm;
13494 
13495 				// Prevent leak in IE
13496 				settings.content_document = settings.content_window = null;
13497 			}
13498 
13499 			// It will not steal focus while setting contentEditable
13500 			body = self.getBody();
13501 			body.disabled = true;
13502 
13503 			if (!settings.readonly)
13504 				body.contentEditable = self.getParam('content_editable_state', true);
13505 
13506 			body.disabled = false;
13507 
13508 			self.schema = new tinymce.html.Schema(settings);
13509 
13510 			self.dom = new tinymce.dom.DOMUtils(doc, {
13511 				keep_values : true,
13512 				url_converter : self.convertURL,
13513 				url_converter_scope : self,
13514 				hex_colors : settings.force_hex_style_colors,
13515 				class_filter : settings.class_filter,
13516 				update_styles : true,
13517 				root_element : settings.content_editable ? self.id : null,
13518 				schema : self.schema
13519 			});
13520 
13521 			self.parser = new tinymce.html.DomParser(settings, self.schema);
13522 
13523 			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
13524 			self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
13525 				var i = nodes.length, node, dom = self.dom, value, internalName;
13526 
13527 				while (i--) {
13528 					node = nodes[i];
13529 					value = node.attr(name);
13530 					internalName = 'data-mce-' + name;
13531 
13532 					// Add internal attribute if we need to we don't on a refresh of the document
13533 					if (!node.attributes.map[internalName]) {	
13534 						if (name === "style")
13535 							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
13536 						else
13537 							node.attr(internalName, self.convertURL(value, name, node.name));
13538 					}
13539 				}
13540 			});
13541 
13542 			// Keep scripts from executing
13543 			self.parser.addNodeFilter('script', function(nodes, name) {
13544 				var i = nodes.length, node;
13545 
13546 				while (i--) {
13547 					node = nodes[i];
13548 					node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
13549 				}
13550 			});
13551 
13552 			self.parser.addNodeFilter('#cdata', function(nodes, name) {
13553 				var i = nodes.length, node;
13554 
13555 				while (i--) {
13556 					node = nodes[i];
13557 					node.type = 8;
13558 					node.name = '#comment';
13559 					node.value = '[CDATA[' + node.value + ']]';
13560 				}
13561 			});
13562 
13563 			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
13564 				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
13565 
13566 				while (i--) {
13567 					node = nodes[i];
13568 
13569 					if (node.isEmpty(nonEmptyElements))
13570 						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
13571 				}
13572 			});
13573 
13574 			self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
13575 
13576 			self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
13577 
13578 			self.formatter = new tinymce.Formatter(self);
13579 
13580 			self.undoManager = new tinymce.UndoManager(self);
13581 
13582 			self.forceBlocks = new tinymce.ForceBlocks(self);
13583 			self.enterKey = new tinymce.EnterKey(self);
13584 			self.editorCommands = new tinymce.EditorCommands(self);
13585 
13586 			self.onExecCommand.add(function(editor, command) {
13587 				// Don't refresh the select lists until caret move
13588 				if (!/^(FontName|FontSize)$/.test(command))
13589 					self.nodeChanged();
13590 			});
13591 
13592 			// Pass through
13593 			self.serializer.onPreProcess.add(function(se, o) {
13594 				return self.onPreProcess.dispatch(self, o, se);
13595 			});
13596 
13597 			self.serializer.onPostProcess.add(function(se, o) {
13598 				return self.onPostProcess.dispatch(self, o, se);
13599 			});
13600 
13601 			self.onPreInit.dispatch(self);
13602 
13603 			if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
13604 				doc.body.spellcheck = false;
13605 
13606 			if (!settings.readonly) {
13607 				self.bindNativeEvents();
13608 			}
13609 
13610 			self.controlManager.onPostRender.dispatch(self, self.controlManager);
13611 			self.onPostRender.dispatch(self);
13612 
13613 			self.quirks = tinymce.util.Quirks(self);
13614 
13615 			if (settings.directionality)
13616 				body.dir = settings.directionality;
13617 
13618 			if (settings.nowrap)
13619 				body.style.whiteSpace = "nowrap";
13620 
13621 			if (settings.protect) {
13622 				self.onBeforeSetContent.add(function(ed, o) {
13623 					each(settings.protect, function(pattern) {
13624 						o.content = o.content.replace(pattern, function(str) {
13625 							return '<!--mce:protected ' + escape(str) + '-->';
13626 						});
13627 					});
13628 				});
13629 			}
13630 
13631 			// Add visual aids when new contents is added
13632 			self.onSetContent.add(function() {
13633 				self.addVisual(self.getBody());
13634 			});
13635 
13636 			// Remove empty contents
13637 			if (settings.padd_empty_editor) {
13638 				self.onPostProcess.add(function(ed, o) {
13639 					o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
13640 				});
13641 			}
13642 
13643 			self.load({initial : true, format : 'html'});
13644 			self.startContent = self.getContent({format : 'raw'});
13645 
13646 			self.initialized = true;
13647 
13648 			self.onInit.dispatch(self);
13649 			self.execCallback('setupcontent_callback', self.id, body, doc);
13650 			self.execCallback('init_instance_callback', self);
13651 			self.focus(true);
13652 			self.nodeChanged({initial : true});
13653 
13654 			// Add editor specific CSS styles
13655 			if (self.contentStyles.length > 0) {
13656 				contentCssText = '';
13657 
13658 				each(self.contentStyles, function(style) {
13659 					contentCssText += style + "\r\n";
13660 				});
13661 
13662 				self.dom.addStyle(contentCssText);
13663 			}
13664 
13665 			// Load specified content CSS last
13666 			each(self.contentCSS, function(url) {
13667 				self.dom.loadCSS(url);
13668 			});
13669 
13670 			// Handle auto focus
13671 			if (settings.auto_focus) {
13672 				setTimeout(function () {
13673 					var ed = tinymce.get(settings.auto_focus);
13674 
13675 					ed.selection.select(ed.getBody(), 1);
13676 					ed.selection.collapse(1);
13677 					ed.getBody().focus();
13678 					ed.getWin().focus();
13679 				}, 100);
13680 			}
13681 
13682 			// Clean up references for IE
13683 			targetElm = doc = body = null;
13684 		},
13685 
13686 		focus : function(skip_focus) {
13687 			var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
13688 
13689 			if (!skip_focus) {
13690 				if (self.lastIERng) {
13691 					selection.setRng(self.lastIERng);
13692 				}
13693 
13694 				// Get selected control element
13695 				ieRng = selection.getRng();
13696 				if (ieRng.item) {
13697 					controlElm = ieRng.item(0);
13698 				}
13699 
13700 				self._refreshContentEditable();
13701 
13702 				// Focus the window iframe
13703 				if (!contentEditable) {
13704 					self.getWin().focus();
13705 				}
13706 
13707 				// Focus the body as well since it's contentEditable
13708 				if (tinymce.isGecko || contentEditable) {
13709 					body = self.getBody();
13710 
13711 					// Check for setActive since it doesn't scroll to the element
13712 					if (body.setActive) {
13713 						body.setActive();
13714 					} else {
13715 						body.focus();
13716 					}
13717 
13718 					if (contentEditable) {
13719 						selection.normalize();
13720 					}
13721 				}
13722 
13723 				// Restore selected control element
13724 				// This is needed when for example an image is selected within a
13725 				// layer a call to focus will then remove the control selection
13726 				if (controlElm && controlElm.ownerDocument == doc) {
13727 					ieRng = doc.body.createControlRange();
13728 					ieRng.addElement(controlElm);
13729 					ieRng.select();
13730 				}
13731 			}
13732 
13733 			if (tinymce.activeEditor != self) {
13734 				if ((oed = tinymce.activeEditor) != null)
13735 					oed.onDeactivate.dispatch(oed, self);
13736 
13737 				self.onActivate.dispatch(self, oed);
13738 			}
13739 
13740 			tinymce._setActive(self);
13741 		},
13742 
13743 		execCallback : function(n) {
13744 			var t = this, f = t.settings[n], s;
13745 
13746 			if (!f)
13747 				return;
13748 
13749 			// Look through lookup
13750 			if (t.callbackLookup && (s = t.callbackLookup[n])) {
13751 				f = s.func;
13752 				s = s.scope;
13753 			}
13754 
13755 			if (is(f, 'string')) {
13756 				s = f.replace(/\.\w+$/, '');
13757 				s = s ? tinymce.resolve(s) : 0;
13758 				f = tinymce.resolve(f);
13759 				t.callbackLookup = t.callbackLookup || {};
13760 				t.callbackLookup[n] = {func : f, scope : s};
13761 			}
13762 
13763 			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
13764 		},
13765 
13766 		translate : function(s) {
13767 			var c = this.settings.language || 'en', i18n = tinymce.i18n;
13768 
13769 			if (!s)
13770 				return '';
13771 
13772 			return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
13773 				return i18n[c + '.' + b] || '{#' + b + '}';
13774 			});
13775 		},
13776 
13777 		getLang : function(n, dv) {
13778 			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
13779 		},
13780 
13781 		getParam : function(n, dv, ty) {
13782 			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
13783 
13784 			if (ty === 'hash') {
13785 				o = {};
13786 
13787 				if (is(v, 'string')) {
13788 					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
13789 						v = v.split('=');
13790 
13791 						if (v.length > 1)
13792 							o[tr(v[0])] = tr(v[1]);
13793 						else
13794 							o[tr(v[0])] = tr(v);
13795 					});
13796 				} else
13797 					o = v;
13798 
13799 				return o;
13800 			}
13801 
13802 			return v;
13803 		},
13804 
13805 		nodeChanged : function(o) {
13806 			var self = this, selection = self.selection, node;
13807 
13808 			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
13809 			if (self.initialized) {
13810 				o = o || {};
13811 
13812 				// Get start node
13813 				node = selection.getStart() || self.getBody();
13814 				node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
13815 
13816 				// Get parents and add them to object
13817 				o.parents = [];
13818 				self.dom.getParent(node, function(node) {
13819 					if (node.nodeName == 'BODY')
13820 						return true;
13821 
13822 					o.parents.push(node);
13823 				});
13824 
13825 				self.onNodeChange.dispatch(
13826 					self,
13827 					o ? o.controlManager || self.controlManager : self.controlManager,
13828 					node,
13829 					selection.isCollapsed(),
13830 					o
13831 				);
13832 			}
13833 		},
13834 
13835 		addButton : function(name, settings) {
13836 			var self = this;
13837 
13838 			self.buttons = self.buttons || {};
13839 			self.buttons[name] = settings;
13840 		},
13841 
13842 		addCommand : function(name, callback, scope) {
13843 			this.execCommands[name] = {func : callback, scope : scope || this};
13844 		},
13845 
13846 		addQueryStateHandler : function(name, callback, scope) {
13847 			this.queryStateCommands[name] = {func : callback, scope : scope || this};
13848 		},
13849 
13850 		addQueryValueHandler : function(name, callback, scope) {
13851 			this.queryValueCommands[name] = {func : callback, scope : scope || this};
13852 		},
13853 
13854 		addShortcut : function(pa, desc, cmd_func, sc) {
13855 			var t = this, c;
13856 
13857 			if (t.settings.custom_shortcuts === false)
13858 				return false;
13859 
13860 			t.shortcuts = t.shortcuts || {};
13861 
13862 			if (is(cmd_func, 'string')) {
13863 				c = cmd_func;
13864 
13865 				cmd_func = function() {
13866 					t.execCommand(c, false, null);
13867 				};
13868 			}
13869 
13870 			if (is(cmd_func, 'object')) {
13871 				c = cmd_func;
13872 
13873 				cmd_func = function() {
13874 					t.execCommand(c[0], c[1], c[2]);
13875 				};
13876 			}
13877 
13878 			each(explode(pa), function(pa) {
13879 				var o = {
13880 					func : cmd_func,
13881 					scope : sc || this,
13882 					desc : t.translate(desc),
13883 					alt : false,
13884 					ctrl : false,
13885 					shift : false
13886 				};
13887 
13888 				each(explode(pa, '+'), function(v) {
13889 					switch (v) {
13890 						case 'alt':
13891 						case 'ctrl':
13892 						case 'shift':
13893 							o[v] = true;
13894 							break;
13895 
13896 						default:
13897 							o.charCode = v.charCodeAt(0);
13898 							o.keyCode = v.toUpperCase().charCodeAt(0);
13899 					}
13900 				});
13901 
13902 				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
13903 			});
13904 
13905 			return true;
13906 		},
13907 
13908 		execCommand : function(cmd, ui, val, a) {
13909 			var t = this, s = 0, o, st;
13910 
13911 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
13912 				t.focus();
13913 
13914 			a = extend({}, a);
13915 			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
13916 			if (a.terminate)
13917 				return false;
13918 
13919 			// Command callback
13920 			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
13921 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13922 				return true;
13923 			}
13924 
13925 			// Registred commands
13926 			if (o = t.execCommands[cmd]) {
13927 				st = o.func.call(o.scope, ui, val);
13928 
13929 				// Fall through on true
13930 				if (st !== true) {
13931 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13932 					return st;
13933 				}
13934 			}
13935 
13936 			// Plugin commands
13937 			each(t.plugins, function(p) {
13938 				if (p.execCommand && p.execCommand(cmd, ui, val)) {
13939 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13940 					s = 1;
13941 					return false;
13942 				}
13943 			});
13944 
13945 			if (s)
13946 				return true;
13947 
13948 			// Theme commands
13949 			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
13950 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13951 				return true;
13952 			}
13953 
13954 			// Editor commands
13955 			if (t.editorCommands.execCommand(cmd, ui, val)) {
13956 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13957 				return true;
13958 			}
13959 
13960 			// Browser commands
13961 			t.getDoc().execCommand(cmd, ui, val);
13962 			t.onExecCommand.dispatch(t, cmd, ui, val, a);
13963 		},
13964 
13965 		queryCommandState : function(cmd) {
13966 			var t = this, o, s;
13967 
13968 			// Is hidden then return undefined
13969 			if (t._isHidden())
13970 				return;
13971 
13972 			// Registred commands
13973 			if (o = t.queryStateCommands[cmd]) {
13974 				s = o.func.call(o.scope);
13975 
13976 				// Fall though on true
13977 				if (s !== true)
13978 					return s;
13979 			}
13980 
13981 			// Registred commands
13982 			o = t.editorCommands.queryCommandState(cmd);
13983 			if (o !== -1)
13984 				return o;
13985 
13986 			// Browser commands
13987 			try {
13988 				return this.getDoc().queryCommandState(cmd);
13989 			} catch (ex) {
13990 				// Fails sometimes see bug: 1896577
13991 			}
13992 		},
13993 
13994 		queryCommandValue : function(c) {
13995 			var t = this, o, s;
13996 
13997 			// Is hidden then return undefined
13998 			if (t._isHidden())
13999 				return;
14000 
14001 			// Registred commands
14002 			if (o = t.queryValueCommands[c]) {
14003 				s = o.func.call(o.scope);
14004 
14005 				// Fall though on true
14006 				if (s !== true)
14007 					return s;
14008 			}
14009 
14010 			// Registred commands
14011 			o = t.editorCommands.queryCommandValue(c);
14012 			if (is(o))
14013 				return o;
14014 
14015 			// Browser commands
14016 			try {
14017 				return this.getDoc().queryCommandValue(c);
14018 			} catch (ex) {
14019 				// Fails sometimes see bug: 1896577
14020 			}
14021 		},
14022 
14023 		show : function() {
14024 			var self = this;
14025 
14026 			DOM.show(self.getContainer());
14027 			DOM.hide(self.id);
14028 			self.load();
14029 		},
14030 
14031 		hide : function() {
14032 			var self = this, doc = self.getDoc();
14033 
14034 			// Fixed bug where IE has a blinking cursor left from the editor
14035 			if (isIE && doc)
14036 				doc.execCommand('SelectAll');
14037 
14038 			// We must save before we hide so Safari doesn't crash
14039 			self.save();
14040 			DOM.hide(self.getContainer());
14041 			DOM.setStyle(self.id, 'display', self.orgDisplay);
14042 		},
14043 
14044 		isHidden : function() {
14045 			return !DOM.isHidden(this.id);
14046 		},
14047 
14048 		setProgressState : function(b, ti, o) {
14049 			this.onSetProgressState.dispatch(this, b, ti, o);
14050 
14051 			return b;
14052 		},
14053 
14054 		load : function(o) {
14055 			var t = this, e = t.getElement(), h;
14056 
14057 			if (e) {
14058 				o = o || {};
14059 				o.load = true;
14060 
14061 				// Double encode existing entities in the value
14062 				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
14063 				o.element = e;
14064 
14065 				if (!o.no_events)
14066 					t.onLoadContent.dispatch(t, o);
14067 
14068 				o.element = e = null;
14069 
14070 				return h;
14071 			}
14072 		},
14073 
14074 		save : function(o) {
14075 			var t = this, e = t.getElement(), h, f;
14076 
14077 			if (!e || !t.initialized)
14078 				return;
14079 
14080 			o = o || {};
14081 			o.save = true;
14082 
14083 			o.element = e;
14084 			h = o.content = t.getContent(o);
14085 
14086 			if (!o.no_events)
14087 				t.onSaveContent.dispatch(t, o);
14088 
14089 			h = o.content;
14090 
14091 			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
14092 				e.innerHTML = h;
14093 
14094 				// Update hidden form element
14095 				if (f = DOM.getParent(t.id, 'form')) {
14096 					each(f.elements, function(e) {
14097 						if (e.name == t.id) {
14098 							e.value = h;
14099 							return false;
14100 						}
14101 					});
14102 				}
14103 			} else
14104 				e.value = h;
14105 
14106 			o.element = e = null;
14107 
14108 			return h;
14109 		},
14110 
14111 		setContent : function(content, args) {
14112 			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
14113 
14114 			// Setup args object
14115 			args = args || {};
14116 			args.format = args.format || 'html';
14117 			args.set = true;
14118 			args.content = content;
14119 
14120 			// Do preprocessing
14121 			if (!args.no_events)
14122 				self.onBeforeSetContent.dispatch(self, args);
14123 
14124 			content = args.content;
14125 
14126 			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
14127 			// It will also be impossible to place the caret in the editor unless there is a BR element present
14128 			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
14129 				forcedRootBlockName = self.settings.forced_root_block;
14130 				if (forcedRootBlockName)
14131 					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
14132 				else
14133 					content = '<br data-mce-bogus="1">';
14134 
14135 				body.innerHTML = content;
14136 				self.selection.select(body, true);
14137 				self.selection.collapse(true);
14138 				return;
14139 			}
14140 
14141 			// Parse and serialize the html
14142 			if (args.format !== 'raw') {
14143 				content = new tinymce.html.Serializer({}, self.schema).serialize(
14144 					self.parser.parse(content)
14145 				);
14146 			}
14147 
14148 			// Set the new cleaned contents to the editor
14149 			args.content = tinymce.trim(content);
14150 			self.dom.setHTML(body, args.content);
14151 
14152 			// Do post processing
14153 			if (!args.no_events)
14154 				self.onSetContent.dispatch(self, args);
14155 
14156 			// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
14157 			if (!self.settings.content_editable || document.activeElement === self.getBody()) {
14158 				self.selection.normalize();
14159 			}
14160 
14161 			return args.content;
14162 		},
14163 
14164 		getContent : function(args) {
14165 			var self = this, content;
14166 
14167 			// Setup args object
14168 			args = args || {};
14169 			args.format = args.format || 'html';
14170 			args.get = true;
14171 			args.getInner = true;
14172 
14173 			// Do preprocessing
14174 			if (!args.no_events)
14175 				self.onBeforeGetContent.dispatch(self, args);
14176 
14177 			// Get raw contents or by default the cleaned contents
14178 			if (args.format == 'raw')
14179 				content = self.getBody().innerHTML;
14180 			else
14181 				content = self.serializer.serialize(self.getBody(), args);
14182 
14183 			args.content = tinymce.trim(content);
14184 
14185 			// Do post processing
14186 			if (!args.no_events)
14187 				self.onGetContent.dispatch(self, args);
14188 
14189 			return args.content;
14190 		},
14191 
14192 		isDirty : function() {
14193 			var self = this;
14194 
14195 			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
14196 		},
14197 
14198 		getContainer : function() {
14199 			var self = this;
14200 
14201 			if (!self.container)
14202 				self.container = DOM.get(self.editorContainer || self.id + '_parent');
14203 
14204 			return self.container;
14205 		},
14206 
14207 		getContentAreaContainer : function() {
14208 			return this.contentAreaContainer;
14209 		},
14210 
14211 		getElement : function() {
14212 			return DOM.get(this.settings.content_element || this.id);
14213 		},
14214 
14215 		getWin : function() {
14216 			var self = this, elm;
14217 
14218 			if (!self.contentWindow) {
14219 				elm = DOM.get(self.id + "_ifr");
14220 
14221 				if (elm)
14222 					self.contentWindow = elm.contentWindow;
14223 			}
14224 
14225 			return self.contentWindow;
14226 		},
14227 
14228 		getDoc : function() {
14229 			var self = this, win;
14230 
14231 			if (!self.contentDocument) {
14232 				win = self.getWin();
14233 
14234 				if (win)
14235 					self.contentDocument = win.document;
14236 			}
14237 
14238 			return self.contentDocument;
14239 		},
14240 
14241 		getBody : function() {
14242 			return this.bodyElement || this.getDoc().body;
14243 		},
14244 
14245 		convertURL : function(url, name, elm) {
14246 			var self = this, settings = self.settings;
14247 
14248 			// Use callback instead
14249 			if (settings.urlconverter_callback)
14250 				return self.execCallback('urlconverter_callback', url, elm, true, name);
14251 
14252 			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
14253 			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
14254 				return url;
14255 
14256 			// Convert to relative
14257 			if (settings.relative_urls)
14258 				return self.documentBaseURI.toRelative(url);
14259 
14260 			// Convert to absolute
14261 			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
14262 
14263 			return url;
14264 		},
14265 
14266 		addVisual : function(elm) {
14267 			var self = this, settings = self.settings, dom = self.dom, cls;
14268 
14269 			elm = elm || self.getBody();
14270 
14271 			if (!is(self.hasVisual))
14272 				self.hasVisual = settings.visual;
14273 
14274 			each(dom.select('table,a', elm), function(elm) {
14275 				var value;
14276 
14277 				switch (elm.nodeName) {
14278 					case 'TABLE':
14279 						cls = settings.visual_table_class || 'mceItemTable';
14280 						value = dom.getAttrib(elm, 'border');
14281 
14282 						if (!value || value == '0') {
14283 							if (self.hasVisual)
14284 								dom.addClass(elm, cls);
14285 							else
14286 								dom.removeClass(elm, cls);
14287 						}
14288 
14289 						return;
14290 
14291 					case 'A':
14292 						if (!dom.getAttrib(elm, 'href', false)) {
14293 							value = dom.getAttrib(elm, 'name') || elm.id;
14294 							cls = 'mceItemAnchor';
14295 
14296 							if (value) {
14297 								if (self.hasVisual)
14298 									dom.addClass(elm, cls);
14299 								else
14300 									dom.removeClass(elm, cls);
14301 							}
14302 						}
14303 
14304 						return;
14305 				}
14306 			});
14307 
14308 			self.onVisualAid.dispatch(self, elm, self.hasVisual);
14309 		},
14310 
14311 		remove : function() {
14312 			var self = this, elm = self.getContainer();
14313 
14314 			if (!self.removed) {
14315 				self.removed = 1; // Cancels post remove event execution
14316 				self.hide();
14317 
14318 				// Don't clear the window or document if content editable
14319 				// is enabled since other instances might still be present
14320 				if (!self.settings.content_editable) {
14321 					Event.unbind(self.getWin());
14322 					Event.unbind(self.getDoc());
14323 				}
14324 
14325 				Event.unbind(self.getBody());
14326 				Event.clear(elm);
14327 
14328 				self.execCallback('remove_instance_callback', self);
14329 				self.onRemove.dispatch(self);
14330 
14331 				// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
14332 				self.onExecCommand.listeners = [];
14333 
14334 				tinymce.remove(self);
14335 				DOM.remove(elm);
14336 			}
14337 		},
14338 
14339 		destroy : function(s) {
14340 			var t = this;
14341 
14342 			// One time is enough
14343 			if (t.destroyed)
14344 				return;
14345 
14346 			// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
14347 			if (isGecko) {
14348 				Event.unbind(t.getDoc());
14349 				Event.unbind(t.getWin());
14350 				Event.unbind(t.getBody());
14351 			}
14352 
14353 			if (!s) {
14354 				tinymce.removeUnload(t.destroy);
14355 				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
14356 
14357 				// Manual destroy
14358 				if (t.theme && t.theme.destroy)
14359 					t.theme.destroy();
14360 
14361 				// Destroy controls, selection and dom
14362 				t.controlManager.destroy();
14363 				t.selection.destroy();
14364 				t.dom.destroy();
14365 			}
14366 
14367 			if (t.formElement) {
14368 				t.formElement.submit = t.formElement._mceOldSubmit;
14369 				t.formElement._mceOldSubmit = null;
14370 			}
14371 
14372 			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
14373 
14374 			if (t.selection)
14375 				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
14376 
14377 			t.destroyed = 1;
14378 		},
14379 
14380 		// Internal functions
14381 
14382 		_refreshContentEditable : function() {
14383 			var self = this, body, parent;
14384 
14385 			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
14386 			if (self._isHidden()) {
14387 				body = self.getBody();
14388 				parent = body.parentNode;
14389 
14390 				parent.removeChild(body);
14391 				parent.appendChild(body);
14392 
14393 				body.focus();
14394 			}
14395 		},
14396 
14397 		_isHidden : function() {
14398 			var s;
14399 
14400 			if (!isGecko)
14401 				return 0;
14402 
14403 			// Weird, wheres that cursor selection?
14404 			s = this.selection.getSel();
14405 			return (!s || !s.rangeCount || s.rangeCount === 0);
14406 		}
14407 	});
14408 })(tinymce);
14409 (function(tinymce) {
14410 	var each = tinymce.each;
14411 
14412 	tinymce.Editor.prototype.setupEvents = function() {
14413 		var self = this, settings = self.settings;
14414 
14415 		// Add events to the editor
14416 		each([
14417 			'onPreInit',
14418 
14419 			'onBeforeRenderUI',
14420 
14421 			'onPostRender',
14422 
14423 			'onLoad',
14424 
14425 			'onInit',
14426 
14427 			'onRemove',
14428 
14429 			'onActivate',
14430 
14431 			'onDeactivate',
14432 
14433 			'onClick',
14434 
14435 			'onEvent',
14436 
14437 			'onMouseUp',
14438 
14439 			'onMouseDown',
14440 
14441 			'onDblClick',
14442 
14443 			'onKeyDown',
14444 
14445 			'onKeyUp',
14446 
14447 			'onKeyPress',
14448 
14449 			'onContextMenu',
14450 
14451 			'onSubmit',
14452 
14453 			'onReset',
14454 
14455 			'onPaste',
14456 
14457 			'onPreProcess',
14458 
14459 			'onPostProcess',
14460 
14461 			'onBeforeSetContent',
14462 
14463 			'onBeforeGetContent',
14464 
14465 			'onSetContent',
14466 
14467 			'onGetContent',
14468 
14469 			'onLoadContent',
14470 
14471 			'onSaveContent',
14472 
14473 			'onNodeChange',
14474 
14475 			'onChange',
14476 
14477 			'onBeforeExecCommand',
14478 
14479 			'onExecCommand',
14480 
14481 			'onUndo',
14482 
14483 			'onRedo',
14484 
14485 			'onVisualAid',
14486 
14487 			'onSetProgressState',
14488 
14489 			'onSetAttrib'
14490 		], function(name) {
14491 			self[name] = new tinymce.util.Dispatcher(self);
14492 		});
14493 
14494 		// Handle legacy cleanup_callback option
14495 		if (settings.cleanup_callback) {
14496 			self.onBeforeSetContent.add(function(ed, o) {
14497 				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14498 			});
14499 
14500 			self.onPreProcess.add(function(ed, o) {
14501 				if (o.set)
14502 					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
14503 
14504 				if (o.get)
14505 					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
14506 			});
14507 
14508 			self.onPostProcess.add(function(ed, o) {
14509 				if (o.set)
14510 					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14511 
14512 				if (o.get)						
14513 					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
14514 			});
14515 		}
14516 
14517 		// Handle legacy save_callback option
14518 		if (settings.save_callback) {
14519 			self.onGetContent.add(function(ed, o) {
14520 				if (o.save)
14521 					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14522 			});
14523 		}
14524 
14525 		// Handle legacy handle_event_callback option
14526 		if (settings.handle_event_callback) {
14527 			self.onEvent.add(function(ed, e, o) {
14528 				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
14529 					e.preventDefault();
14530 					e.stopPropagation();
14531 				}
14532 			});
14533 		}
14534 
14535 		// Handle legacy handle_node_change_callback option
14536 		if (settings.handle_node_change_callback) {
14537 			self.onNodeChange.add(function(ed, cm, n) {
14538 				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
14539 			});
14540 		}
14541 
14542 		// Handle legacy save_callback option
14543 		if (settings.save_callback) {
14544 			self.onSaveContent.add(function(ed, o) {
14545 				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14546 
14547 				if (h)
14548 					o.content = h;
14549 			});
14550 		}
14551 
14552 		// Handle legacy onchange_callback option
14553 		if (settings.onchange_callback) {
14554 			self.onChange.add(function(ed, l) {
14555 				ed.execCallback('onchange_callback', ed, l);
14556 			});
14557 		}
14558 	};
14559 
14560 	tinymce.Editor.prototype.bindNativeEvents = function() {
14561 		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
14562 		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
14563 
14564 		nativeToDispatcherMap = {
14565 			mouseup : 'onMouseUp',
14566 			mousedown : 'onMouseDown',
14567 			click : 'onClick',
14568 			keyup : 'onKeyUp',
14569 			keydown : 'onKeyDown',
14570 			keypress : 'onKeyPress',
14571 			submit : 'onSubmit',
14572 			reset : 'onReset',
14573 			contextmenu : 'onContextMenu',
14574 			dblclick : 'onDblClick',
14575 			paste : 'onPaste' // Doesn't work in all browsers yet
14576 		};
14577 
14578 		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
14579 		function eventHandler(evt, args) {
14580 			var type = evt.type;
14581 
14582 			// Don't fire events when it's removed
14583 			if (self.removed)
14584 				return;
14585 
14586 			// Sends the native event out to a global dispatcher then to the specific event dispatcher
14587 			if (self.onEvent.dispatch(self, evt, args) !== false) {
14588 				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
14589 			}
14590 		};
14591 
14592 		// Opera doesn't support focus event for contentEditable elements so we need to fake it
14593 		function doOperaFocus(e) {
14594 			self.focus(true);
14595 		};
14596 
14597 		function nodeChanged(ed, e) {
14598 			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
14599 			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
14600 				self.selection.normalize();
14601 			}
14602 
14603 			self.nodeChanged();
14604 		}
14605 
14606 		// Add DOM events
14607 		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
14608 			var root = settings.content_editable ? self.getBody() : self.getDoc();
14609 
14610 			switch (nativeName) {
14611 				case 'contextmenu':
14612 					dom.bind(root, nativeName, eventHandler);
14613 					break;
14614 
14615 				case 'paste':
14616 					dom.bind(self.getBody(), nativeName, eventHandler);
14617 					break;
14618 
14619 				case 'submit':
14620 				case 'reset':
14621 					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
14622 					break;
14623 
14624 				default:
14625 					dom.bind(root, nativeName, eventHandler);
14626 			}
14627 		});
14628 
14629 		// Set the editor as active when focused
14630 		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
14631 			self.focus(true);
14632 		});
14633 
14634 		if (settings.content_editable && tinymce.isOpera) {
14635 			dom.bind(self.getBody(), 'click', doOperaFocus);
14636 			dom.bind(self.getBody(), 'keydown', doOperaFocus);
14637 		}
14638 
14639 		// Add node change handler
14640 		self.onMouseUp.add(nodeChanged);
14641 
14642 		self.onKeyUp.add(function(ed, e) {
14643 			var keyCode = e.keyCode;
14644 
14645 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
14646 				nodeChanged(ed, e);
14647 		});
14648 
14649 		// Add reset handler
14650 		self.onReset.add(function() {
14651 			self.setContent(self.startContent, {format : 'raw'});
14652 		});
14653 
14654 		// Add shortcuts
14655 		function handleShortcut(e, execute) {
14656 			if (e.altKey || e.ctrlKey || e.metaKey) {
14657 				each(self.shortcuts, function(shortcut) {
14658 					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
14659 
14660 					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
14661 						return;
14662 
14663 					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
14664 						e.preventDefault();
14665 
14666 						if (execute) {
14667 							shortcut.func.call(shortcut.scope);
14668 						}
14669 
14670 						return true;
14671 					}
14672 				});
14673 			}
14674 		};
14675 
14676 		self.onKeyUp.add(function(ed, e) {
14677 			handleShortcut(e);
14678 		});
14679 
14680 		self.onKeyPress.add(function(ed, e) {
14681 			handleShortcut(e);
14682 		});
14683 
14684 		self.onKeyDown.add(function(ed, e) {
14685 			handleShortcut(e, true);
14686 		});
14687 
14688 		if (tinymce.isOpera) {
14689 			self.onClick.add(function(ed, e) {
14690 				e.preventDefault();
14691 			});
14692 		}
14693 	};
14694 })(tinymce);
14695 (function(tinymce) {
14696 	// Added for compression purposes
14697 	var each = tinymce.each, undef, TRUE = true, FALSE = false;
14698 
14699 	tinymce.EditorCommands = function(editor) {
14700 		var dom = editor.dom,
14701 			selection = editor.selection,
14702 			commands = {state: {}, exec : {}, value : {}},
14703 			settings = editor.settings,
14704 			formatter = editor.formatter,
14705 			bookmark;
14706 
14707 		function execCommand(command, ui, value) {
14708 			var func;
14709 
14710 			command = command.toLowerCase();
14711 			if (func = commands.exec[command]) {
14712 				func(command, ui, value);
14713 				return TRUE;
14714 			}
14715 
14716 			return FALSE;
14717 		};
14718 
14719 		function queryCommandState(command) {
14720 			var func;
14721 
14722 			command = command.toLowerCase();
14723 			if (func = commands.state[command])
14724 				return func(command);
14725 
14726 			return -1;
14727 		};
14728 
14729 		function queryCommandValue(command) {
14730 			var func;
14731 
14732 			command = command.toLowerCase();
14733 			if (func = commands.value[command])
14734 				return func(command);
14735 
14736 			return FALSE;
14737 		};
14738 
14739 		function addCommands(command_list, type) {
14740 			type = type || 'exec';
14741 
14742 			each(command_list, function(callback, command) {
14743 				each(command.toLowerCase().split(','), function(command) {
14744 					commands[type][command] = callback;
14745 				});
14746 			});
14747 		};
14748 
14749 		// Expose public methods
14750 		tinymce.extend(this, {
14751 			execCommand : execCommand,
14752 			queryCommandState : queryCommandState,
14753 			queryCommandValue : queryCommandValue,
14754 			addCommands : addCommands
14755 		});
14756 
14757 		// Private methods
14758 
14759 		function execNativeCommand(command, ui, value) {
14760 			if (ui === undef)
14761 				ui = FALSE;
14762 
14763 			if (value === undef)
14764 				value = null;
14765 
14766 			return editor.getDoc().execCommand(command, ui, value);
14767 		};
14768 
14769 		function isFormatMatch(name) {
14770 			return formatter.match(name);
14771 		};
14772 
14773 		function toggleFormat(name, value) {
14774 			formatter.toggle(name, value ? {value : value} : undef);
14775 		};
14776 
14777 		function storeSelection(type) {
14778 			bookmark = selection.getBookmark(type);
14779 		};
14780 
14781 		function restoreSelection() {
14782 			selection.moveToBookmark(bookmark);
14783 		};
14784 
14785 		// Add execCommand overrides
14786 		addCommands({
14787 			// Ignore these, added for compatibility
14788 			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
14789 
14790 			// Add undo manager logic
14791 			'mceEndUndoLevel,mceAddUndoLevel' : function() {
14792 				editor.undoManager.add();
14793 			},
14794 
14795 			'Cut,Copy,Paste' : function(command) {
14796 				var doc = editor.getDoc(), failed;
14797 
14798 				// Try executing the native command
14799 				try {
14800 					execNativeCommand(command);
14801 				} catch (ex) {
14802 					// Command failed
14803 					failed = TRUE;
14804 				}
14805 
14806 				// Present alert message about clipboard access not being available
14807 				if (failed || !doc.queryCommandSupported(command)) {
14808 					if (tinymce.isGecko) {
14809 						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
14810 							if (state)
14811 								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
14812 						});
14813 					} else
14814 						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
14815 				}
14816 			},
14817 
14818 			// Override unlink command
14819 			unlink : function(command) {
14820 				if (selection.isCollapsed())
14821 					selection.select(selection.getNode());
14822 
14823 				execNativeCommand(command);
14824 				selection.collapse(FALSE);
14825 			},
14826 
14827 			// Override justify commands to use the text formatter engine
14828 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
14829 				var align = command.substring(7);
14830 
14831 				// Remove all other alignments first
14832 				each('left,center,right,full'.split(','), function(name) {
14833 					if (align != name)
14834 						formatter.remove('align' + name);
14835 				});
14836 
14837 				toggleFormat('align' + align);
14838 				execCommand('mceRepaint');
14839 			},
14840 
14841 			// Override list commands to fix WebKit bug
14842 			'InsertUnorderedList,InsertOrderedList' : function(command) {
14843 				var listElm, listParent;
14844 
14845 				execNativeCommand(command);
14846 
14847 				// WebKit produces lists within block elements so we need to split them
14848 				// we will replace the native list creation logic to custom logic later on
14849 				// TODO: Remove this when the list creation logic is removed
14850 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
14851 				if (listElm) {
14852 					listParent = listElm.parentNode;
14853 
14854 					// If list is within a text block then split that block
14855 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
14856 						storeSelection();
14857 						dom.split(listParent, listElm);
14858 						restoreSelection();
14859 					}
14860 				}
14861 			},
14862 
14863 			// Override commands to use the text formatter engine
14864 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
14865 				toggleFormat(command);
14866 			},
14867 
14868 			// Override commands to use the text formatter engine
14869 			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
14870 				toggleFormat(command, value);
14871 			},
14872 
14873 			FontSize : function(command, ui, value) {
14874 				var fontClasses, fontSizes;
14875 
14876 				// Convert font size 1-7 to styles
14877 				if (value >= 1 && value <= 7) {
14878 					fontSizes = tinymce.explode(settings.font_size_style_values);
14879 					fontClasses = tinymce.explode(settings.font_size_classes);
14880 
14881 					if (fontClasses)
14882 						value = fontClasses[value - 1] || value;
14883 					else
14884 						value = fontSizes[value - 1] || value;
14885 				}
14886 
14887 				toggleFormat(command, value);
14888 			},
14889 
14890 			RemoveFormat : function(command) {
14891 				formatter.remove(command);
14892 			},
14893 
14894 			mceBlockQuote : function(command) {
14895 				toggleFormat('blockquote');
14896 			},
14897 
14898 			FormatBlock : function(command, ui, value) {
14899 				return toggleFormat(value || 'p');
14900 			},
14901 
14902 			mceCleanup : function() {
14903 				var bookmark = selection.getBookmark();
14904 
14905 				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
14906 
14907 				selection.moveToBookmark(bookmark);
14908 			},
14909 
14910 			mceRemoveNode : function(command, ui, value) {
14911 				var node = value || selection.getNode();
14912 
14913 				// Make sure that the body node isn't removed
14914 				if (node != editor.getBody()) {
14915 					storeSelection();
14916 					editor.dom.remove(node, TRUE);
14917 					restoreSelection();
14918 				}
14919 			},
14920 
14921 			mceSelectNodeDepth : function(command, ui, value) {
14922 				var counter = 0;
14923 
14924 				dom.getParent(selection.getNode(), function(node) {
14925 					if (node.nodeType == 1 && counter++ == value) {
14926 						selection.select(node);
14927 						return FALSE;
14928 					}
14929 				}, editor.getBody());
14930 			},
14931 
14932 			mceSelectNode : function(command, ui, value) {
14933 				selection.select(value);
14934 			},
14935 
14936 			mceInsertContent : function(command, ui, value) {
14937 				var parser, serializer, parentNode, rootNode, fragment, args,
14938 					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
14939 
14940 				//selection.normalize();
14941 
14942 				// Setup parser and serializer
14943 				parser = editor.parser;
14944 				serializer = new tinymce.html.Serializer({}, editor.schema);
14945 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
14946 
14947 				// Run beforeSetContent handlers on the HTML to be inserted
14948 				args = {content: value, format: 'html'};
14949 				selection.onBeforeSetContent.dispatch(selection, args);
14950 				value = args.content;
14951 
14952 				// Add caret at end of contents if it's missing
14953 				if (value.indexOf('{$caret}') == -1)
14954 					value += '{$caret}';
14955 
14956 				// Replace the caret marker with a span bookmark element
14957 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
14958 
14959 				// Insert node maker where we will insert the new HTML and get it's parent
14960 				if (!selection.isCollapsed())
14961 					editor.getDoc().execCommand('Delete', false, null);
14962 
14963 				parentNode = selection.getNode();
14964 
14965 				// Parse the fragment within the context of the parent node
14966 				args = {context : parentNode.nodeName.toLowerCase()};
14967 				fragment = parser.parse(value, args);
14968 
14969 				// Move the caret to a more suitable location
14970 				node = fragment.lastChild;
14971 				if (node.attr('id') == 'mce_marker') {
14972 					marker = node;
14973 
14974 					for (node = node.prev; node; node = node.walk(true)) {
14975 						if (node.type == 3 || !dom.isBlock(node.name)) {
14976 							node.parent.insert(marker, node, node.name === 'br');
14977 							break;
14978 						}
14979 					}
14980 				}
14981 
14982 				// If parser says valid we can insert the contents into that parent
14983 				if (!args.invalid) {
14984 					value = serializer.serialize(fragment);
14985 
14986 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
14987 					node = parentNode.firstChild;
14988 					node2 = parentNode.lastChild;
14989 					if (!node || (node === node2 && node.nodeName === 'BR'))
14990 						dom.setHTML(parentNode, value);
14991 					else
14992 						selection.setContent(value);
14993 				} else {
14994 					// If the fragment was invalid within that context then we need
14995 					// to parse and process the parent it's inserted into
14996 
14997 					// Insert bookmark node and get the parent
14998 					selection.setContent(bookmarkHtml);
14999 					parentNode = editor.selection.getNode();
15000 					rootNode = editor.getBody();
15001 
15002 					// Opera will return the document node when selection is in root
15003 					if (parentNode.nodeType == 9)
15004 						parentNode = node = rootNode;
15005 					else
15006 						node = parentNode;
15007 
15008 					// Find the ancestor just before the root element
15009 					while (node !== rootNode) {
15010 						parentNode = node;
15011 						node = node.parentNode;
15012 					}
15013 
15014 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
15015 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
15016 					value = serializer.serialize(
15017 						parser.parse(
15018 							// Need to replace by using a function since $ in the contents would otherwise be a problem
15019 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
15020 								return serializer.serialize(fragment);
15021 							})
15022 						)
15023 					);
15024 
15025 					// Set the inner/outer HTML depending on if we are in the root or not
15026 					if (parentNode == rootNode)
15027 						dom.setHTML(rootNode, value);
15028 					else
15029 						dom.setOuterHTML(parentNode, value);
15030 				}
15031 
15032 				marker = dom.get('mce_marker');
15033 
15034 				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
15035 				nodeRect = dom.getRect(marker);
15036 				viewPortRect = dom.getViewPort(editor.getWin());
15037 
15038 				// Check if node is out side the viewport if it is then scroll to it
15039 				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
15040 					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
15041 					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
15042 					viewportBodyElement.scrollLeft = nodeRect.x;
15043 					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
15044 				}
15045 
15046 				// Move selection before marker and remove it
15047 				rng = dom.createRng();
15048 
15049 				// If previous sibling is a text node set the selection to the end of that node
15050 				node = marker.previousSibling;
15051 				if (node && node.nodeType == 3) {
15052 					rng.setStart(node, node.nodeValue.length);
15053 				} else {
15054 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
15055 					rng.setStartBefore(marker);
15056 					rng.setEndBefore(marker);
15057 				}
15058 
15059 				// Remove the marker node and set the new range
15060 				dom.remove(marker);
15061 				selection.setRng(rng);
15062 
15063 				// Dispatch after event and add any visual elements needed
15064 				selection.onSetContent.dispatch(selection, args);
15065 				editor.addVisual();
15066 			},
15067 
15068 			mceInsertRawHTML : function(command, ui, value) {
15069 				selection.setContent('tiny_mce_marker');
15070 				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
15071 			},
15072 
15073 			mceToggleFormat : function(command, ui, value) {
15074 				toggleFormat(value);
15075 			},
15076 
15077 			mceSetContent : function(command, ui, value) {
15078 				editor.setContent(value);
15079 			},
15080 
15081 			'Indent,Outdent' : function(command) {
15082 				var intentValue, indentUnit, value;
15083 
15084 				// Setup indent level
15085 				intentValue = settings.indentation;
15086 				indentUnit = /[a-z%]+$/i.exec(intentValue);
15087 				intentValue = parseInt(intentValue);
15088 
15089 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
15090 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
15091 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
15092 						formatter.apply('div');
15093 					}
15094 
15095 					each(selection.getSelectedBlocks(), function(element) {
15096 						if (command == 'outdent') {
15097 							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
15098 							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
15099 						} else
15100 							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
15101 					});
15102 				} else
15103 					execNativeCommand(command);
15104 			},
15105 
15106 			mceRepaint : function() {
15107 				var bookmark;
15108 
15109 				if (tinymce.isGecko) {
15110 					try {
15111 						storeSelection(TRUE);
15112 
15113 						if (selection.getSel())
15114 							selection.getSel().selectAllChildren(editor.getBody());
15115 
15116 						selection.collapse(TRUE);
15117 						restoreSelection();
15118 					} catch (ex) {
15119 						// Ignore
15120 					}
15121 				}
15122 			},
15123 
15124 			mceToggleFormat : function(command, ui, value) {
15125 				formatter.toggle(value);
15126 			},
15127 
15128 			InsertHorizontalRule : function() {
15129 				editor.execCommand('mceInsertContent', false, '<hr />');
15130 			},
15131 
15132 			mceToggleVisualAid : function() {
15133 				editor.hasVisual = !editor.hasVisual;
15134 				editor.addVisual();
15135 			},
15136 
15137 			mceReplaceContent : function(command, ui, value) {
15138 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
15139 			},
15140 
15141 			mceInsertLink : function(command, ui, value) {
15142 				var anchor;
15143 
15144 				if (typeof(value) == 'string')
15145 					value = {href : value};
15146 
15147 				anchor = dom.getParent(selection.getNode(), 'a');
15148 
15149 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
15150 				value.href = value.href.replace(' ', '%20');
15151 
15152 				// Remove existing links if there could be child links or that the href isn't specified
15153 				if (!anchor || !value.href) {
15154 					formatter.remove('link');
15155 				}		
15156 
15157 				// Apply new link to selection
15158 				if (value.href) {
15159 					formatter.apply('link', value, anchor);
15160 				}
15161 			},
15162 
15163 			selectAll : function() {
15164 				var root = dom.getRoot(), rng = dom.createRng();
15165 
15166 				rng.setStart(root, 0);
15167 				rng.setEnd(root, root.childNodes.length);
15168 
15169 				editor.selection.setRng(rng);
15170 			}
15171 		});
15172 
15173 		// Add queryCommandState overrides
15174 		addCommands({
15175 			// Override justify commands
15176 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15177 				var name = 'align' + command.substring(7);
15178 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
15179 				var matches = tinymce.map(nodes, function(node) {
15180 					return !!formatter.matchNode(node, name);
15181 				});
15182 				return tinymce.inArray(matches, TRUE) !== -1;
15183 			},
15184 
15185 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15186 				return isFormatMatch(command);
15187 			},
15188 
15189 			mceBlockQuote : function() {
15190 				return isFormatMatch('blockquote');
15191 			},
15192 
15193 			Outdent : function() {
15194 				var node;
15195 
15196 				if (settings.inline_styles) {
15197 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15198 						return TRUE;
15199 
15200 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15201 						return TRUE;
15202 				}
15203 
15204 				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
15205 			},
15206 
15207 			'InsertUnorderedList,InsertOrderedList' : function(command) {
15208 				return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
15209 			}
15210 		}, 'state');
15211 
15212 		// Add queryCommandValue overrides
15213 		addCommands({
15214 			'FontSize,FontName' : function(command) {
15215 				var value = 0, parent;
15216 
15217 				if (parent = dom.getParent(selection.getNode(), 'span')) {
15218 					if (command == 'fontsize')
15219 						value = parent.style.fontSize;
15220 					else
15221 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
15222 				}
15223 
15224 				return value;
15225 			}
15226 		}, 'value');
15227 
15228 		// Add undo manager logic
15229 		addCommands({
15230 			Undo : function() {
15231 				editor.undoManager.undo();
15232 			},
15233 
15234 			Redo : function() {
15235 				editor.undoManager.redo();
15236 			}
15237 		});
15238 	};
15239 })(tinymce);
15240 
15241 (function(tinymce) {
15242 	var Dispatcher = tinymce.util.Dispatcher;
15243 
15244 	tinymce.UndoManager = function(editor) {
15245 		var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
15246 
15247 		function getContent() {
15248 			// Remove whitespace before/after and remove pure bogus nodes
15249 			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
15250 		};
15251 
15252 		function addNonTypingUndoLevel() {
15253 			self.typing = false;
15254 			self.add();
15255 		};
15256 
15257 		// Create event instances
15258 		onBeforeAdd = new Dispatcher(self);
15259 		onAdd       = new Dispatcher(self);
15260 		onUndo      = new Dispatcher(self);
15261 		onRedo      = new Dispatcher(self);
15262 
15263 		// Pass though onAdd event from UndoManager to Editor as onChange
15264 		onAdd.add(function(undoman, level) {
15265 			if (undoman.hasUndo())
15266 				return editor.onChange.dispatch(editor, level, undoman);
15267 		});
15268 
15269 		// Pass though onUndo event from UndoManager to Editor
15270 		onUndo.add(function(undoman, level) {
15271 			return editor.onUndo.dispatch(editor, level, undoman);
15272 		});
15273 
15274 		// Pass though onRedo event from UndoManager to Editor
15275 		onRedo.add(function(undoman, level) {
15276 			return editor.onRedo.dispatch(editor, level, undoman);
15277 		});
15278 
15279 		// Add initial undo level when the editor is initialized
15280 		editor.onInit.add(function() {
15281 			self.add();
15282 		});
15283 
15284 		// Get position before an execCommand is processed
15285 		editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
15286 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15287 				self.beforeChange();
15288 			}
15289 		});
15290 
15291 		// Add undo level after an execCommand call was made
15292 		editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
15293 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15294 				self.add();
15295 			}
15296 		});
15297 
15298 		// Add undo level on save contents, drag end and blur/focusout
15299 		editor.onSaveContent.add(addNonTypingUndoLevel);
15300 		editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
15301 		editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {
15302 			if (!editor.removed && self.typing) {
15303 				addNonTypingUndoLevel();
15304 			}
15305 		});
15306 
15307 		editor.onKeyUp.add(function(editor, e) {
15308 			var keyCode = e.keyCode;
15309 
15310 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
15311 				addNonTypingUndoLevel();
15312 			}
15313 		});
15314 
15315 		editor.onKeyDown.add(function(editor, e) {
15316 			var keyCode = e.keyCode;
15317 
15318 			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
15319 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
15320 				if (self.typing) {
15321 					addNonTypingUndoLevel();
15322 				}
15323 
15324 				return;
15325 			}
15326 
15327 			// If key isn't shift,ctrl,alt,capslock,metakey
15328 			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
15329 				self.beforeChange();
15330 				self.typing = true;
15331 				self.add();
15332 			}
15333 		});
15334 
15335 		editor.onMouseDown.add(function(editor, e) {
15336 			if (self.typing) {
15337 				addNonTypingUndoLevel();
15338 			}
15339 		});
15340 
15341 		// Add keyboard shortcuts for undo/redo keys
15342 		editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
15343 		editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
15344 
15345 		self = {
15346 			// Explose for debugging reasons
15347 			data : data,
15348 
15349 			typing : false,
15350 			
15351 			onBeforeAdd: onBeforeAdd,
15352 
15353 			onAdd : onAdd,
15354 
15355 			onUndo : onUndo,
15356 
15357 			onRedo : onRedo,
15358 
15359 			beforeChange : function() {
15360 				beforeBookmark = editor.selection.getBookmark(2, true);
15361 			},
15362 
15363 			add : function(level) {
15364 				var i, settings = editor.settings, lastLevel;
15365 
15366 				level = level || {};
15367 				level.content = getContent();
15368 				
15369 				self.onBeforeAdd.dispatch(self, level);
15370 
15371 				// Add undo level if needed
15372 				lastLevel = data[index];
15373 				if (lastLevel && lastLevel.content == level.content)
15374 					return null;
15375 
15376 				// Set before bookmark on previous level
15377 				if (data[index])
15378 					data[index].beforeBookmark = beforeBookmark;
15379 
15380 				// Time to compress
15381 				if (settings.custom_undo_redo_levels) {
15382 					if (data.length > settings.custom_undo_redo_levels) {
15383 						for (i = 0; i < data.length - 1; i++)
15384 							data[i] = data[i + 1];
15385 
15386 						data.length--;
15387 						index = data.length;
15388 					}
15389 				}
15390 
15391 				// Get a non intrusive normalized bookmark
15392 				level.bookmark = editor.selection.getBookmark(2, true);
15393 
15394 				// Crop array if needed
15395 				if (index < data.length - 1)
15396 					data.length = index + 1;
15397 
15398 				data.push(level);
15399 				index = data.length - 1;
15400 
15401 				self.onAdd.dispatch(self, level);
15402 				editor.isNotDirty = 0;
15403 
15404 				return level;
15405 			},
15406 
15407 			undo : function() {
15408 				var level, i;
15409 
15410 				if (self.typing) {
15411 					self.add();
15412 					self.typing = false;
15413 				}
15414 
15415 				if (index > 0) {
15416 					level = data[--index];
15417 
15418 					editor.setContent(level.content, {format : 'raw'});
15419 					editor.selection.moveToBookmark(level.beforeBookmark);
15420 
15421 					self.onUndo.dispatch(self, level);
15422 				}
15423 
15424 				return level;
15425 			},
15426 
15427 			redo : function() {
15428 				var level;
15429 
15430 				if (index < data.length - 1) {
15431 					level = data[++index];
15432 
15433 					editor.setContent(level.content, {format : 'raw'});
15434 					editor.selection.moveToBookmark(level.bookmark);
15435 
15436 					self.onRedo.dispatch(self, level);
15437 				}
15438 
15439 				return level;
15440 			},
15441 
15442 			clear : function() {
15443 				data = [];
15444 				index = 0;
15445 				self.typing = false;
15446 			},
15447 
15448 			hasUndo : function() {
15449 				return index > 0 || this.typing;
15450 			},
15451 
15452 			hasRedo : function() {
15453 				return index < data.length - 1 && !this.typing;
15454 			}
15455 		};
15456 
15457 		return self;
15458 	};
15459 })(tinymce);
15460 
15461 tinymce.ForceBlocks = function(editor) {
15462 	var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
15463 
15464 	function addRootBlocks() {
15465 		var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
15466 
15467 		if (!node || node.nodeType !== 1 || !settings.forced_root_block)
15468 			return;
15469 
15470 		// Check if node is wrapped in block
15471 		while (node && node != rootNode) {
15472 			if (blockElements[node.nodeName])
15473 				return;
15474 
15475 			node = node.parentNode;
15476 		}
15477 
15478 		// Get current selection
15479 		rng = selection.getRng();
15480 		if (rng.setStart) {
15481 			startContainer = rng.startContainer;
15482 			startOffset = rng.startOffset;
15483 			endContainer = rng.endContainer;
15484 			endOffset = rng.endOffset;
15485 		} else {
15486 			// Force control range into text range
15487 			if (rng.item) {
15488 				node = rng.item(0);
15489 				rng = editor.getDoc().body.createTextRange();
15490 				rng.moveToElementText(node);
15491 			}
15492 
15493 			isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
15494 			tmpRng = rng.duplicate();
15495 			tmpRng.collapse(true);
15496 			startOffset = tmpRng.move('character', offset) * -1;
15497 
15498 			if (!tmpRng.collapsed) {
15499 				tmpRng = rng.duplicate();
15500 				tmpRng.collapse(false);
15501 				endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
15502 			}
15503 		}
15504 
15505 		// Wrap non block elements and text nodes
15506 		node = rootNode.firstChild;
15507 		while (node) {
15508 			if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
15509 				if (!rootBlockNode) {
15510 					rootBlockNode = dom.create(settings.forced_root_block);
15511 					node.parentNode.insertBefore(rootBlockNode, node);
15512 					wrapped = true;
15513 				}
15514 
15515 				tempNode = node;
15516 				node = node.nextSibling;
15517 				rootBlockNode.appendChild(tempNode);
15518 			} else {
15519 				rootBlockNode = null;
15520 				node = node.nextSibling;
15521 			}
15522 		}
15523 
15524 		if (wrapped) {
15525 			if (rng.setStart) {
15526 				rng.setStart(startContainer, startOffset);
15527 				rng.setEnd(endContainer, endOffset);
15528 				selection.setRng(rng);
15529 			} else {
15530 				// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
15531 				if (isInEditorDocument) {
15532 					try {
15533 						rng = editor.getDoc().body.createTextRange();
15534 						rng.moveToElementText(rootNode);
15535 						rng.collapse(true);
15536 						rng.moveStart('character', startOffset);
15537 
15538 						if (endOffset > 0)
15539 							rng.moveEnd('character', endOffset);
15540 
15541 						rng.select();
15542 					} catch (ex) {
15543 						// Ignore
15544 					}
15545 				}
15546 			}
15547 
15548 			editor.nodeChanged();
15549 		}
15550 	};
15551 
15552 	// Force root blocks
15553 	if (settings.forced_root_block) {
15554 		editor.onKeyUp.add(addRootBlocks);
15555 		editor.onNodeChange.add(addRootBlocks);
15556 	}
15557 };
15558 
15559 (function(tinymce) {
15560 	// Shorten names
15561 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
15562 
15563 	tinymce.create('tinymce.ControlManager', {
15564 		ControlManager : function(ed, s) {
15565 			var t = this, i;
15566 
15567 			s = s || {};
15568 			t.editor = ed;
15569 			t.controls = {};
15570 			t.onAdd = new tinymce.util.Dispatcher(t);
15571 			t.onPostRender = new tinymce.util.Dispatcher(t);
15572 			t.prefix = s.prefix || ed.id + '_';
15573 			t._cls = {};
15574 
15575 			t.onPostRender.add(function() {
15576 				each(t.controls, function(c) {
15577 					c.postRender();
15578 				});
15579 			});
15580 		},
15581 
15582 		get : function(id) {
15583 			return this.controls[this.prefix + id] || this.controls[id];
15584 		},
15585 
15586 		setActive : function(id, s) {
15587 			var c = null;
15588 
15589 			if (c = this.get(id))
15590 				c.setActive(s);
15591 
15592 			return c;
15593 		},
15594 
15595 		setDisabled : function(id, s) {
15596 			var c = null;
15597 
15598 			if (c = this.get(id))
15599 				c.setDisabled(s);
15600 
15601 			return c;
15602 		},
15603 
15604 		add : function(c) {
15605 			var t = this;
15606 
15607 			if (c) {
15608 				t.controls[c.id] = c;
15609 				t.onAdd.dispatch(c, t);
15610 			}
15611 
15612 			return c;
15613 		},
15614 
15615 		createControl : function(name) {
15616 			var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
15617 
15618 			// Build control factory cache
15619 			if (!self.controlFactories) {
15620 				self.controlFactories = [];
15621 				each(editor.plugins, function(plugin) {
15622 					if (plugin.createControl) {
15623 						self.controlFactories.push(plugin);
15624 					}
15625 				});
15626 			}
15627 
15628 			// Create controls by asking cached factories
15629 			factories = self.controlFactories;
15630 			for (i = 0, l = factories.length; i < l; i++) {
15631 				ctrl = factories[i].createControl(name, self);
15632 
15633 				if (ctrl) {
15634 					return self.add(ctrl);
15635 				}
15636 			}
15637 
15638 			// Create sepearator
15639 			if (name === "|" || name === "separator") {
15640 				return self.createSeparator();
15641 			}
15642 
15643 			// Create control from button collection
15644 			if (editor.buttons && (ctrl = editor.buttons[name])) {
15645 				return self.createButton(name, ctrl);
15646 			}
15647 
15648 			return self.add(ctrl);
15649 		},
15650 
15651 		createDropMenu : function(id, s, cc) {
15652 			var t = this, ed = t.editor, c, bm, v, cls;
15653 
15654 			s = extend({
15655 				'class' : 'mceDropDown',
15656 				constrain : ed.settings.constrain_menus
15657 			}, s);
15658 
15659 			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
15660 			if (v = ed.getParam('skin_variant'))
15661 				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
15662 
15663 			s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
15664 
15665 			id = t.prefix + id;
15666 			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
15667 			c = t.controls[id] = new cls(id, s);
15668 			c.onAddItem.add(function(c, o) {
15669 				var s = o.settings;
15670 
15671 				s.title = ed.getLang(s.title, s.title);
15672 
15673 				if (!s.onclick) {
15674 					s.onclick = function(v) {
15675 						if (s.cmd)
15676 							ed.execCommand(s.cmd, s.ui || false, s.value);
15677 					};
15678 				}
15679 			});
15680 
15681 			ed.onRemove.add(function() {
15682 				c.destroy();
15683 			});
15684 
15685 			// Fix for bug #1897785, #1898007
15686 			if (tinymce.isIE) {
15687 				c.onShowMenu.add(function() {
15688 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15689 					ed.focus();
15690 
15691 					bm = ed.selection.getBookmark(1);
15692 				});
15693 
15694 				c.onHideMenu.add(function() {
15695 					if (bm) {
15696 						ed.selection.moveToBookmark(bm);
15697 						bm = 0;
15698 					}
15699 				});
15700 			}
15701 
15702 			return t.add(c);
15703 		},
15704 
15705 		createListBox : function(id, s, cc) {
15706 			var t = this, ed = t.editor, cmd, c, cls;
15707 
15708 			if (t.get(id))
15709 				return null;
15710 
15711 			s.title = ed.translate(s.title);
15712 			s.scope = s.scope || ed;
15713 
15714 			if (!s.onselect) {
15715 				s.onselect = function(v) {
15716 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15717 				};
15718 			}
15719 
15720 			s = extend({
15721 				title : s.title,
15722 				'class' : 'mce_' + id,
15723 				scope : s.scope,
15724 				control_manager : t
15725 			}, s);
15726 
15727 			id = t.prefix + id;
15728 
15729 
15730 			function useNativeListForAccessibility(ed) {
15731 				return ed.settings.use_accessible_selects && !tinymce.isGecko
15732 			}
15733 
15734 			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
15735 				c = new tinymce.ui.NativeListBox(id, s);
15736 			else {
15737 				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
15738 				c = new cls(id, s, ed);
15739 			}
15740 
15741 			t.controls[id] = c;
15742 
15743 			// Fix focus problem in Safari
15744 			if (tinymce.isWebKit) {
15745 				c.onPostRender.add(function(c, n) {
15746 					// Store bookmark on mousedown
15747 					Event.add(n, 'mousedown', function() {
15748 						ed.bookmark = ed.selection.getBookmark(1);
15749 					});
15750 
15751 					// Restore on focus, since it might be lost
15752 					Event.add(n, 'focus', function() {
15753 						ed.selection.moveToBookmark(ed.bookmark);
15754 						ed.bookmark = null;
15755 					});
15756 				});
15757 			}
15758 
15759 			if (c.hideMenu)
15760 				ed.onMouseDown.add(c.hideMenu, c);
15761 
15762 			return t.add(c);
15763 		},
15764 
15765 		createButton : function(id, s, cc) {
15766 			var t = this, ed = t.editor, o, c, cls;
15767 
15768 			if (t.get(id))
15769 				return null;
15770 
15771 			s.title = ed.translate(s.title);
15772 			s.label = ed.translate(s.label);
15773 			s.scope = s.scope || ed;
15774 
15775 			if (!s.onclick && !s.menu_button) {
15776 				s.onclick = function() {
15777 					ed.execCommand(s.cmd, s.ui || false, s.value);
15778 				};
15779 			}
15780 
15781 			s = extend({
15782 				title : s.title,
15783 				'class' : 'mce_' + id,
15784 				unavailable_prefix : ed.getLang('unavailable', ''),
15785 				scope : s.scope,
15786 				control_manager : t
15787 			}, s);
15788 
15789 			id = t.prefix + id;
15790 
15791 			if (s.menu_button) {
15792 				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
15793 				c = new cls(id, s, ed);
15794 				ed.onMouseDown.add(c.hideMenu, c);
15795 			} else {
15796 				cls = t._cls.button || tinymce.ui.Button;
15797 				c = new cls(id, s, ed);
15798 			}
15799 
15800 			return t.add(c);
15801 		},
15802 
15803 		createMenuButton : function(id, s, cc) {
15804 			s = s || {};
15805 			s.menu_button = 1;
15806 
15807 			return this.createButton(id, s, cc);
15808 		},
15809 
15810 		createSplitButton : function(id, s, cc) {
15811 			var t = this, ed = t.editor, cmd, c, cls;
15812 
15813 			if (t.get(id))
15814 				return null;
15815 
15816 			s.title = ed.translate(s.title);
15817 			s.scope = s.scope || ed;
15818 
15819 			if (!s.onclick) {
15820 				s.onclick = function(v) {
15821 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15822 				};
15823 			}
15824 
15825 			if (!s.onselect) {
15826 				s.onselect = function(v) {
15827 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15828 				};
15829 			}
15830 
15831 			s = extend({
15832 				title : s.title,
15833 				'class' : 'mce_' + id,
15834 				scope : s.scope,
15835 				control_manager : t
15836 			}, s);
15837 
15838 			id = t.prefix + id;
15839 			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
15840 			c = t.add(new cls(id, s, ed));
15841 			ed.onMouseDown.add(c.hideMenu, c);
15842 
15843 			return c;
15844 		},
15845 
15846 		createColorSplitButton : function(id, s, cc) {
15847 			var t = this, ed = t.editor, cmd, c, cls, bm;
15848 
15849 			if (t.get(id))
15850 				return null;
15851 
15852 			s.title = ed.translate(s.title);
15853 			s.scope = s.scope || ed;
15854 
15855 			if (!s.onclick) {
15856 				s.onclick = function(v) {
15857 					if (tinymce.isIE)
15858 						bm = ed.selection.getBookmark(1);
15859 
15860 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15861 				};
15862 			}
15863 
15864 			if (!s.onselect) {
15865 				s.onselect = function(v) {
15866 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15867 				};
15868 			}
15869 
15870 			s = extend({
15871 				title : s.title,
15872 				'class' : 'mce_' + id,
15873 				'menu_class' : ed.getParam('skin') + 'Skin',
15874 				scope : s.scope,
15875 				more_colors_title : ed.getLang('more_colors')
15876 			}, s);
15877 
15878 			id = t.prefix + id;
15879 			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
15880 			c = new cls(id, s, ed);
15881 			ed.onMouseDown.add(c.hideMenu, c);
15882 
15883 			// Remove the menu element when the editor is removed
15884 			ed.onRemove.add(function() {
15885 				c.destroy();
15886 			});
15887 
15888 			// Fix for bug #1897785, #1898007
15889 			if (tinymce.isIE) {
15890 				c.onShowMenu.add(function() {
15891 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15892 					ed.focus();
15893 					bm = ed.selection.getBookmark(1);
15894 				});
15895 
15896 				c.onHideMenu.add(function() {
15897 					if (bm) {
15898 						ed.selection.moveToBookmark(bm);
15899 						bm = 0;
15900 					}
15901 				});
15902 			}
15903 
15904 			return t.add(c);
15905 		},
15906 
15907 		createToolbar : function(id, s, cc) {
15908 			var c, t = this, cls;
15909 
15910 			id = t.prefix + id;
15911 			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
15912 			c = new cls(id, s, t.editor);
15913 
15914 			if (t.get(id))
15915 				return null;
15916 
15917 			return t.add(c);
15918 		},
15919 		
15920 		createToolbarGroup : function(id, s, cc) {
15921 			var c, t = this, cls;
15922 			id = t.prefix + id;
15923 			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
15924 			c = new cls(id, s, t.editor);
15925 			
15926 			if (t.get(id))
15927 				return null;
15928 			
15929 			return t.add(c);
15930 		},
15931 
15932 		createSeparator : function(cc) {
15933 			var cls = cc || this._cls.separator || tinymce.ui.Separator;
15934 
15935 			return new cls();
15936 		},
15937 
15938 		setControlType : function(n, c) {
15939 			return this._cls[n.toLowerCase()] = c;
15940 		},
15941 	
15942 		destroy : function() {
15943 			each(this.controls, function(c) {
15944 				c.destroy();
15945 			});
15946 
15947 			this.controls = null;
15948 		}
15949 	});
15950 })(tinymce);
15951 
15952 (function(tinymce) {
15953 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
15954 
15955 	tinymce.create('tinymce.WindowManager', {
15956 		WindowManager : function(ed) {
15957 			var t = this;
15958 
15959 			t.editor = ed;
15960 			t.onOpen = new Dispatcher(t);
15961 			t.onClose = new Dispatcher(t);
15962 			t.params = {};
15963 			t.features = {};
15964 		},
15965 
15966 		open : function(s, p) {
15967 			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
15968 
15969 			// Default some options
15970 			s = s || {};
15971 			p = p || {};
15972 			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
15973 			sh = isOpera ? vp.h : screen.height;
15974 			s.name = s.name || 'mc_' + new Date().getTime();
15975 			s.width = parseInt(s.width || 320);
15976 			s.height = parseInt(s.height || 240);
15977 			s.resizable = true;
15978 			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
15979 			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
15980 			p.inline = false;
15981 			p.mce_width = s.width;
15982 			p.mce_height = s.height;
15983 			p.mce_auto_focus = s.auto_focus;
15984 
15985 			if (mo) {
15986 				if (isIE) {
15987 					s.center = true;
15988 					s.help = false;
15989 					s.dialogWidth = s.width + 'px';
15990 					s.dialogHeight = s.height + 'px';
15991 					s.scroll = s.scrollbars || false;
15992 				}
15993 			}
15994 
15995 			// Build features string
15996 			each(s, function(v, k) {
15997 				if (tinymce.is(v, 'boolean'))
15998 					v = v ? 'yes' : 'no';
15999 
16000 				if (!/^(name|url)$/.test(k)) {
16001 					if (isIE && mo)
16002 						f += (f ? ';' : '') + k + ':' + v;
16003 					else
16004 						f += (f ? ',' : '') + k + '=' + v;
16005 				}
16006 			});
16007 
16008 			t.features = s;
16009 			t.params = p;
16010 			t.onOpen.dispatch(t, s, p);
16011 
16012 			u = s.url || s.file;
16013 			u = tinymce._addVer(u);
16014 
16015 			try {
16016 				if (isIE && mo) {
16017 					w = 1;
16018 					window.showModalDialog(u, window, f);
16019 				} else
16020 					w = window.open(u, s.name, f);
16021 			} catch (ex) {
16022 				// Ignore
16023 			}
16024 
16025 			if (!w)
16026 				alert(t.editor.getLang('popup_blocked'));
16027 		},
16028 
16029 		close : function(w) {
16030 			w.close();
16031 			this.onClose.dispatch(this);
16032 		},
16033 
16034 		createInstance : function(cl, a, b, c, d, e) {
16035 			var f = tinymce.resolve(cl);
16036 
16037 			return new f(a, b, c, d, e);
16038 		},
16039 
16040 		confirm : function(t, cb, s, w) {
16041 			w = w || window;
16042 
16043 			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
16044 		},
16045 
16046 		alert : function(tx, cb, s, w) {
16047 			var t = this;
16048 
16049 			w = w || window;
16050 			w.alert(t._decode(t.editor.getLang(tx, tx)));
16051 
16052 			if (cb)
16053 				cb.call(s || t);
16054 		},
16055 
16056 		resizeBy : function(dw, dh, win) {
16057 			win.resizeBy(dw, dh);
16058 		},
16059 
16060 		// Internal functions
16061 
16062 		_decode : function(s) {
16063 			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
16064 		}
16065 	});
16066 }(tinymce));
16067 (function(tinymce) {
16068 	tinymce.Formatter = function(ed) {
16069 		var formats = {},
16070 			each = tinymce.each,
16071 			dom = ed.dom,
16072 			selection = ed.selection,
16073 			TreeWalker = tinymce.dom.TreeWalker,
16074 			rangeUtils = new tinymce.dom.RangeUtils(dom),
16075 			isValid = ed.schema.isValidChild,
16076 			isBlock = dom.isBlock,
16077 			forcedRootBlock = ed.settings.forced_root_block,
16078 			nodeIndex = dom.nodeIndex,
16079 			INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
16080 			MCE_ATTR_RE = /^(src|href|style)$/,
16081 			FALSE = false,
16082 			TRUE = true,
16083 			formatChangeData,
16084 			undef,
16085 			getContentEditable = dom.getContentEditable;
16086 
16087 		function isArray(obj) {
16088 			return obj instanceof Array;
16089 		};
16090 
16091 		function getParents(node, selector) {
16092 			return dom.getParents(node, selector, dom.getRoot());
16093 		};
16094 
16095 		function isCaretNode(node) {
16096 			return node.nodeType === 1 && node.id === '_mce_caret';
16097 		};
16098 
16099 		function defaultFormats() {
16100 			register({
16101 				alignleft : [
16102 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
16103 					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
16104 				],
16105 
16106 				aligncenter : [
16107 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
16108 					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
16109 					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
16110 				],
16111 
16112 				alignright : [
16113 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
16114 					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
16115 				],
16116 
16117 				alignfull : [
16118 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
16119 				],
16120 
16121 				bold : [
16122 					{inline : 'strong', remove : 'all'},
16123 					{inline : 'span', styles : {fontWeight : 'bold'}},
16124 					{inline : 'b', remove : 'all'}
16125 				],
16126 
16127 				italic : [
16128 					{inline : 'em', remove : 'all'},
16129 					{inline : 'span', styles : {fontStyle : 'italic'}},
16130 					{inline : 'i', remove : 'all'}
16131 				],
16132 
16133 				underline : [
16134 					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
16135 					{inline : 'u', remove : 'all'}
16136 				],
16137 
16138 				strikethrough : [
16139 					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
16140 					{inline : 'strike', remove : 'all'}
16141 				],
16142 
16143 				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
16144 				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
16145 				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
16146 				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
16147 				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
16148 				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
16149 				subscript : {inline : 'sub'},
16150 				superscript : {inline : 'sup'},
16151 
16152 				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
16153 					onmatch : function(node) {
16154 						return true;
16155 					},
16156 
16157 					onformat : function(elm, fmt, vars) {
16158 						each(vars, function(value, key) {
16159 							dom.setAttrib(elm, key, value);
16160 						});
16161 					}
16162 				},
16163 
16164 				removeformat : [
16165 					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
16166 					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
16167 					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
16168 				]
16169 			});
16170 
16171 			// Register default block formats
16172 			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
16173 				register(name, {block : name, remove : 'all'});
16174 			});
16175 
16176 			// Register user defined formats
16177 			register(ed.settings.formats);
16178 		};
16179 
16180 		function addKeyboardShortcuts() {
16181 			// Add some inline shortcuts
16182 			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
16183 			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
16184 			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
16185 
16186 			// BlockFormat shortcuts keys
16187 			for (var i = 1; i <= 6; i++) {
16188 				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
16189 			}
16190 
16191 			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
16192 			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
16193 			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
16194 		};
16195 
16196 		// Public functions
16197 
16198 		function get(name) {
16199 			return name ? formats[name] : formats;
16200 		};
16201 
16202 		function register(name, format) {
16203 			if (name) {
16204 				if (typeof(name) !== 'string') {
16205 					each(name, function(format, name) {
16206 						register(name, format);
16207 					});
16208 				} else {
16209 					// Force format into array and add it to internal collection
16210 					format = format.length ? format : [format];
16211 
16212 					each(format, function(format) {
16213 						// Set deep to false by default on selector formats this to avoid removing
16214 						// alignment on images inside paragraphs when alignment is changed on paragraphs
16215 						if (format.deep === undef)
16216 							format.deep = !format.selector;
16217 
16218 						// Default to true
16219 						if (format.split === undef)
16220 							format.split = !format.selector || format.inline;
16221 
16222 						// Default to true
16223 						if (format.remove === undef && format.selector && !format.inline)
16224 							format.remove = 'none';
16225 
16226 						// Mark format as a mixed format inline + block level
16227 						if (format.selector && format.inline) {
16228 							format.mixed = true;
16229 							format.block_expand = true;
16230 						}
16231 
16232 						// Split classes if needed
16233 						if (typeof(format.classes) === 'string')
16234 							format.classes = format.classes.split(/\s+/);
16235 					});
16236 
16237 					formats[name] = format;
16238 				}
16239 			}
16240 		};
16241 
16242 		var getTextDecoration = function(node) {
16243 			var decoration;
16244 
16245 			ed.dom.getParent(node, function(n) {
16246 				decoration = ed.dom.getStyle(n, 'text-decoration');
16247 				return decoration && decoration !== 'none';
16248 			});
16249 
16250 			return decoration;
16251 		};
16252 
16253 		var processUnderlineAndColor = function(node) {
16254 			var textDecoration;
16255 			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
16256 				textDecoration = getTextDecoration(node.parentNode);
16257 				if (ed.dom.getStyle(node, 'color') && textDecoration) {
16258 					ed.dom.setStyle(node, 'text-decoration', textDecoration);
16259 				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
16260 					ed.dom.setStyle(node, 'text-decoration', null);
16261 				}
16262 			}
16263 		};
16264 
16265 		function apply(name, vars, node) {
16266 			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
16267 
16268 			function setElementFormat(elm, fmt) {
16269 				fmt = fmt || format;
16270 
16271 				if (elm) {
16272 					if (fmt.onformat) {
16273 						fmt.onformat(elm, fmt, vars, node);
16274 					}
16275 
16276 					each(fmt.styles, function(value, name) {
16277 						dom.setStyle(elm, name, replaceVars(value, vars));
16278 					});
16279 
16280 					each(fmt.attributes, function(value, name) {
16281 						dom.setAttrib(elm, name, replaceVars(value, vars));
16282 					});
16283 
16284 					each(fmt.classes, function(value) {
16285 						value = replaceVars(value, vars);
16286 
16287 						if (!dom.hasClass(elm, value))
16288 							dom.addClass(elm, value);
16289 					});
16290 				}
16291 			};
16292 			function adjustSelectionToVisibleSelection() {
16293 				function findSelectionEnd(start, end) {
16294 					var walker = new TreeWalker(end);
16295 					for (node = walker.current(); node; node = walker.prev()) {
16296 						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
16297 							return node;
16298 						}
16299 					}
16300 				};
16301 
16302 				// Adjust selection so that a end container with a end offset of zero is not included in the selection
16303 				// as this isn't visible to the user.
16304 				var rng = ed.selection.getRng();
16305 				var start = rng.startContainer;
16306 				var end = rng.endContainer;
16307 
16308 				if (start != end && rng.endOffset === 0) {
16309 					var newEnd = findSelectionEnd(start, end);
16310 					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
16311 
16312 					rng.setEnd(newEnd, endOffset);
16313 				}
16314 
16315 				return rng;
16316 			}
16317 			
16318 			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
16319 				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
16320 				
16321 				// find the index of the first child list.
16322 				each(node.childNodes, function(n, index) {
16323 					if (n.nodeName === "UL" || n.nodeName === "OL") {
16324 						listIndex = index;
16325 						list = n;
16326 						return false;
16327 					}
16328 				});
16329 				
16330 				// get the index of the bookmarks
16331 				each(node.childNodes, function(n, index) {
16332 					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
16333 						if (n.id == bookmark.id + "_start") {
16334 							startIndex = index;
16335 						} else if (n.id == bookmark.id + "_end") {
16336 							endIndex = index;
16337 						}
16338 					}
16339 				});
16340 				
16341 				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
16342 				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
16343 					each(tinymce.grep(node.childNodes), process);
16344 					return 0;
16345 				} else {
16346 					currentWrapElm = dom.clone(wrapElm, FALSE);
16347 
16348 					// create a list of the nodes on the same side of the list as the selection
16349 					each(tinymce.grep(node.childNodes), function(n, index) {
16350 						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
16351 							nodes.push(n); 
16352 							n.parentNode.removeChild(n);
16353 						}
16354 					});
16355 
16356 					// insert the wrapping element either before or after the list.
16357 					if (startIndex < listIndex) {
16358 						node.insertBefore(currentWrapElm, list);
16359 					} else if (startIndex > listIndex) {
16360 						node.insertBefore(currentWrapElm, list.nextSibling);
16361 					}
16362 					
16363 					// add the new nodes to the list.
16364 					newWrappers.push(currentWrapElm);
16365 
16366 					each(nodes, function(node) {
16367 						currentWrapElm.appendChild(node);
16368 					});
16369 
16370 					return currentWrapElm;
16371 				}
16372 			};
16373 
16374 			function applyRngStyle(rng, bookmark, node_specific) {
16375 				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
16376 
16377 				// Setup wrapper element
16378 				wrapName = format.inline || format.block;
16379 				wrapElm = dom.create(wrapName);
16380 				setElementFormat(wrapElm);
16381 
16382 				rangeUtils.walk(rng, function(nodes) {
16383 					var currentWrapElm;
16384 
16385 					function process(node) {
16386 						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
16387 
16388 						lastContentEditable = contentEditable;
16389 						nodeName = node.nodeName.toLowerCase();
16390 						parentName = node.parentNode.nodeName.toLowerCase();
16391 
16392 						// Node has a contentEditable value
16393 						if (node.nodeType === 1 && getContentEditable(node)) {
16394 							lastContentEditable = contentEditable;
16395 							contentEditable = getContentEditable(node) === "true";
16396 							hasContentEditableState = true; // We don't want to wrap the container only it's children
16397 						}
16398 
16399 						// Stop wrapping on br elements
16400 						if (isEq(nodeName, 'br')) {
16401 							currentWrapElm = 0;
16402 
16403 							// Remove any br elements when we wrap things
16404 							if (format.block)
16405 								dom.remove(node);
16406 
16407 							return;
16408 						}
16409 
16410 						// If node is wrapper type
16411 						if (format.wrapper && matchNode(node, name, vars)) {
16412 							currentWrapElm = 0;
16413 							return;
16414 						}
16415 
16416 						// Can we rename the block
16417 						if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
16418 							node = dom.rename(node, wrapName);
16419 							setElementFormat(node);
16420 							newWrappers.push(node);
16421 							currentWrapElm = 0;
16422 							return;
16423 						}
16424 
16425 						// Handle selector patterns
16426 						if (format.selector) {
16427 							// Look for matching formats
16428 							each(formatList, function(format) {
16429 								// Check collapsed state if it exists
16430 								if ('collapsed' in format && format.collapsed !== isCollapsed) {
16431 									return;
16432 								}
16433 
16434 								if (dom.is(node, format.selector) && !isCaretNode(node)) {
16435 									setElementFormat(node, format);
16436 									found = true;
16437 								}
16438 							});
16439 
16440 							// Continue processing if a selector match wasn't found and a inline element is defined
16441 							if (!format.inline || found) {
16442 								currentWrapElm = 0;
16443 								return;
16444 							}
16445 						}
16446 
16447 						// Is it valid to wrap this item
16448 						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
16449 								!(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
16450 							// Start wrapping
16451 							if (!currentWrapElm) {
16452 								// Wrap the node
16453 								currentWrapElm = dom.clone(wrapElm, FALSE);
16454 								node.parentNode.insertBefore(currentWrapElm, node);
16455 								newWrappers.push(currentWrapElm);
16456 							}
16457 
16458 							currentWrapElm.appendChild(node);
16459 						} else if (nodeName == 'li' && bookmark) {
16460 							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
16461 							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
16462 						} else {
16463 							// Start a new wrapper for possible children
16464 							currentWrapElm = 0;
16465 							
16466 							each(tinymce.grep(node.childNodes), process);
16467 
16468 							if (hasContentEditableState) {
16469 								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16470 							}
16471 
16472 							// End the last wrapper
16473 							currentWrapElm = 0;
16474 						}
16475 					};
16476 
16477 					// Process siblings from range
16478 					each(nodes, process);
16479 				});
16480 
16481 				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
16482 				if (format.wrap_links === false) {
16483 					each(newWrappers, function(node) {
16484 						function process(node) {
16485 							var i, currentWrapElm, children;
16486 
16487 							if (node.nodeName === 'A') {
16488 								currentWrapElm = dom.clone(wrapElm, FALSE);
16489 								newWrappers.push(currentWrapElm);
16490 
16491 								children = tinymce.grep(node.childNodes);
16492 								for (i = 0; i < children.length; i++)
16493 									currentWrapElm.appendChild(children[i]);
16494 
16495 								node.appendChild(currentWrapElm);
16496 							}
16497 
16498 							each(tinymce.grep(node.childNodes), process);
16499 						};
16500 
16501 						process(node);
16502 					});
16503 				}
16504 
16505 				// Cleanup
16506 				
16507 				each(newWrappers, function(node) {
16508 					var childCount;
16509 
16510 					function getChildCount(node) {
16511 						var count = 0;
16512 
16513 						each(node.childNodes, function(node) {
16514 							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
16515 								count++;
16516 						});
16517 
16518 						return count;
16519 					};
16520 
16521 					function mergeStyles(node) {
16522 						var child, clone;
16523 
16524 						each(node.childNodes, function(node) {
16525 							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
16526 								child = node;
16527 								return FALSE; // break loop
16528 							}
16529 						});
16530 
16531 						// If child was found and of the same type as the current node
16532 						if (child && matchName(child, format)) {
16533 							clone = dom.clone(child, FALSE);
16534 							setElementFormat(clone);
16535 
16536 							dom.replace(clone, node, TRUE);
16537 							dom.remove(child, 1);
16538 						}
16539 
16540 						return clone || node;
16541 					};
16542 
16543 					childCount = getChildCount(node);
16544 
16545 					// Remove empty nodes but only if there is multiple wrappers and they are not block
16546 					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
16547 					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
16548 						dom.remove(node, 1);
16549 						return;
16550 					}
16551 
16552 					if (format.inline || format.wrapper) {
16553 						// Merges the current node with it's children of similar type to reduce the number of elements
16554 						if (!format.exact && childCount === 1)
16555 							node = mergeStyles(node);
16556 
16557 						// Remove/merge children
16558 						each(formatList, function(format) {
16559 							// Merge all children of similar type will move styles from child to parent
16560 							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
16561 							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
16562 							each(dom.select(format.inline, node), function(child) {
16563 								var parent;
16564 
16565 								// When wrap_links is set to false we don't want
16566 								// to remove the format on children within links
16567 								if (format.wrap_links === false) {
16568 									parent = child.parentNode;
16569 
16570 									do {
16571 										if (parent.nodeName === 'A')
16572 											return;
16573 									} while (parent = parent.parentNode);
16574 								}
16575 
16576 								removeFormat(format, vars, child, format.exact ? child : null);
16577 							});
16578 						});
16579 
16580 						// Remove child if direct parent is of same type
16581 						if (matchNode(node.parentNode, name, vars)) {
16582 							dom.remove(node, 1);
16583 							node = 0;
16584 							return TRUE;
16585 						}
16586 
16587 						// Look for parent with similar style format
16588 						if (format.merge_with_parents) {
16589 							dom.getParent(node.parentNode, function(parent) {
16590 								if (matchNode(parent, name, vars)) {
16591 									dom.remove(node, 1);
16592 									node = 0;
16593 									return TRUE;
16594 								}
16595 							});
16596 						}
16597 
16598 						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
16599 						if (node && format.merge_siblings !== false) {
16600 							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
16601 							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
16602 						}
16603 					}
16604 				});
16605 			};
16606 
16607 			if (format) {
16608 				if (node) {
16609 					if (node.nodeType) {
16610 						rng = dom.createRng();
16611 						rng.setStartBefore(node);
16612 						rng.setEndAfter(node);
16613 						applyRngStyle(expandRng(rng, formatList), null, true);
16614 					} else {
16615 						applyRngStyle(node, null, true);
16616 					}
16617 				} else {
16618 					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16619 						// Obtain selection node before selection is unselected by applyRngStyle()
16620 						var curSelNode = ed.selection.getNode();
16621 
16622 						// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
16623 						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
16624 						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
16625 							apply(formatList[0].defaultBlock);
16626 						}
16627 
16628 						// Apply formatting to selection
16629 						ed.selection.setRng(adjustSelectionToVisibleSelection());
16630 						bookmark = selection.getBookmark();
16631 						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
16632 
16633 						// Colored nodes should be underlined so that the color of the underline matches the text color.
16634 						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
16635 							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
16636 							processUnderlineAndColor(curSelNode);
16637 						}
16638 
16639 						selection.moveToBookmark(bookmark);
16640 						moveStart(selection.getRng(TRUE));
16641 						ed.nodeChanged();
16642 					} else
16643 						performCaretAction('apply', name, vars);
16644 				}
16645 			}
16646 		};
16647 
16648 		function remove(name, vars, node) {
16649 			var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
16650 
16651 			// Merges the styles for each node
16652 			function process(node) {
16653 				var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
16654 
16655 				// Node has a contentEditable value
16656 				if (node.nodeType === 1 && getContentEditable(node)) {
16657 					lastContentEditable = contentEditable;
16658 					contentEditable = getContentEditable(node) === "true";
16659 					hasContentEditableState = true; // We don't want to wrap the container only it's children
16660 				}
16661 
16662 				// Grab the children first since the nodelist might be changed
16663 				children = tinymce.grep(node.childNodes);
16664 
16665 				// Process current node
16666 				if (contentEditable && !hasContentEditableState) {
16667 					for (i = 0, l = formatList.length; i < l; i++) {
16668 						if (removeFormat(formatList[i], vars, node, node))
16669 							break;
16670 					}
16671 				}
16672 
16673 				// Process the children
16674 				if (format.deep) {
16675 					if (children.length) {					
16676 						for (i = 0, l = children.length; i < l; i++)
16677 							process(children[i]);
16678 
16679 						if (hasContentEditableState) {
16680 							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16681 						}
16682 					}
16683 				}
16684 			};
16685 
16686 			function findFormatRoot(container) {
16687 				var formatRoot;
16688 
16689 				// Find format root
16690 				each(getParents(container.parentNode).reverse(), function(parent) {
16691 					var format;
16692 
16693 					// Find format root element
16694 					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
16695 						// Is the node matching the format we are looking for
16696 						format = matchNode(parent, name, vars);
16697 						if (format && format.split !== false)
16698 							formatRoot = parent;
16699 					}
16700 				});
16701 
16702 				return formatRoot;
16703 			};
16704 
16705 			function wrapAndSplit(format_root, container, target, split) {
16706 				var parent, clone, lastClone, firstClone, i, formatRootParent;
16707 
16708 				// Format root found then clone formats and split it
16709 				if (format_root) {
16710 					formatRootParent = format_root.parentNode;
16711 
16712 					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
16713 						clone = dom.clone(parent, FALSE);
16714 
16715 						for (i = 0; i < formatList.length; i++) {
16716 							if (removeFormat(formatList[i], vars, clone, clone)) {
16717 								clone = 0;
16718 								break;
16719 							}
16720 						}
16721 
16722 						// Build wrapper node
16723 						if (clone) {
16724 							if (lastClone)
16725 								clone.appendChild(lastClone);
16726 
16727 							if (!firstClone)
16728 								firstClone = clone;
16729 
16730 							lastClone = clone;
16731 						}
16732 					}
16733 
16734 					// Never split block elements if the format is mixed
16735 					if (split && (!format.mixed || !isBlock(format_root)))
16736 						container = dom.split(format_root, container);
16737 
16738 					// Wrap container in cloned formats
16739 					if (lastClone) {
16740 						target.parentNode.insertBefore(lastClone, target);
16741 						firstClone.appendChild(target);
16742 					}
16743 				}
16744 
16745 				return container;
16746 			};
16747 
16748 			function splitToFormatRoot(container) {
16749 				return wrapAndSplit(findFormatRoot(container), container, container, true);
16750 			};
16751 
16752 			function unwrap(start) {
16753 				var node = dom.get(start ? '_start' : '_end'),
16754 					out = node[start ? 'firstChild' : 'lastChild'];
16755 
16756 				// If the end is placed within the start the result will be removed
16757 				// So this checks if the out node is a bookmark node if it is it
16758 				// checks for another more suitable node
16759 				if (isBookmarkNode(out))
16760 					out = out[start ? 'firstChild' : 'lastChild'];
16761 
16762 				dom.remove(node, true);
16763 
16764 				return out;
16765 			};
16766 
16767 			function removeRngStyle(rng) {
16768 				var startContainer, endContainer, node;
16769 
16770 				rng = expandRng(rng, formatList, TRUE);
16771 
16772 				if (format.split) {
16773 					startContainer = getContainer(rng, TRUE);
16774 					endContainer = getContainer(rng);
16775 
16776 					if (startContainer != endContainer) {
16777 						// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
16778 						// This will happen if you tripple click a table cell and use remove formatting
16779 						if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
16780 							startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
16781 						}
16782 
16783 						// Wrap start/end nodes in span element since these might be cloned/moved
16784 						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
16785 						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
16786 
16787 						// Split start/end
16788 						splitToFormatRoot(startContainer);
16789 						splitToFormatRoot(endContainer);
16790 
16791 						// Unwrap start/end to get real elements again
16792 						startContainer = unwrap(TRUE);
16793 						endContainer = unwrap();
16794 					} else
16795 						startContainer = endContainer = splitToFormatRoot(startContainer);
16796 
16797 					// Update range positions since they might have changed after the split operations
16798 					rng.startContainer = startContainer.parentNode;
16799 					rng.startOffset = nodeIndex(startContainer);
16800 					rng.endContainer = endContainer.parentNode;
16801 					rng.endOffset = nodeIndex(endContainer) + 1;
16802 				}
16803 
16804 				// Remove items between start/end
16805 				rangeUtils.walk(rng, function(nodes) {
16806 					each(nodes, function(node) {
16807 						process(node);
16808 
16809 						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
16810 						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
16811 							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
16812 						}
16813 					});
16814 				});
16815 			};
16816 
16817 			// Handle node
16818 			if (node) {
16819 				if (node.nodeType) {
16820 					rng = dom.createRng();
16821 					rng.setStartBefore(node);
16822 					rng.setEndAfter(node);
16823 					removeRngStyle(rng);
16824 				} else {
16825 					removeRngStyle(node);
16826 				}
16827 
16828 				return;
16829 			}
16830 
16831 			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16832 				bookmark = selection.getBookmark();
16833 				removeRngStyle(selection.getRng(TRUE));
16834 				selection.moveToBookmark(bookmark);
16835 
16836 				// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
16837 				if (format.inline && match(name, vars, selection.getStart())) {
16838 					moveStart(selection.getRng(true));
16839 				}
16840 
16841 				ed.nodeChanged();
16842 			} else
16843 				performCaretAction('remove', name, vars);
16844 		};
16845 
16846 		function toggle(name, vars, node) {
16847 			var fmt = get(name);
16848 
16849 			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
16850 				remove(name, vars, node);
16851 			else
16852 				apply(name, vars, node);
16853 		};
16854 
16855 		function matchNode(node, name, vars, similar) {
16856 			var formatList = get(name), format, i, classes;
16857 
16858 			function matchItems(node, format, item_name) {
16859 				var key, value, items = format[item_name], i;
16860 
16861 				// Custom match
16862 				if (format.onmatch) {
16863 					return format.onmatch(node, format, item_name);
16864 				}
16865 
16866 				// Check all items
16867 				if (items) {
16868 					// Non indexed object
16869 					if (items.length === undef) {
16870 						for (key in items) {
16871 							if (items.hasOwnProperty(key)) {
16872 								if (item_name === 'attributes')
16873 									value = dom.getAttrib(node, key);
16874 								else
16875 									value = getStyle(node, key);
16876 
16877 								if (similar && !value && !format.exact)
16878 									return;
16879 
16880 								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
16881 									return;
16882 							}
16883 						}
16884 					} else {
16885 						// Only one match needed for indexed arrays
16886 						for (i = 0; i < items.length; i++) {
16887 							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
16888 								return format;
16889 						}
16890 					}
16891 				}
16892 
16893 				return format;
16894 			};
16895 
16896 			if (formatList && node) {
16897 				// Check each format in list
16898 				for (i = 0; i < formatList.length; i++) {
16899 					format = formatList[i];
16900 
16901 					// Name name, attributes, styles and classes
16902 					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
16903 						// Match classes
16904 						if (classes = format.classes) {
16905 							for (i = 0; i < classes.length; i++) {
16906 								if (!dom.hasClass(node, classes[i]))
16907 									return;
16908 							}
16909 						}
16910 
16911 						return format;
16912 					}
16913 				}
16914 			}
16915 		};
16916 
16917 		function match(name, vars, node) {
16918 			var startNode;
16919 
16920 			function matchParents(node) {
16921 				// Find first node with similar format settings
16922 				node = dom.getParent(node, function(node) {
16923 					return !!matchNode(node, name, vars, true);
16924 				});
16925 
16926 				// Do an exact check on the similar format element
16927 				return matchNode(node, name, vars);
16928 			};
16929 
16930 			// Check specified node
16931 			if (node)
16932 				return matchParents(node);
16933 
16934 			// Check selected node
16935 			node = selection.getNode();
16936 			if (matchParents(node))
16937 				return TRUE;
16938 
16939 			// Check start node if it's different
16940 			startNode = selection.getStart();
16941 			if (startNode != node) {
16942 				if (matchParents(startNode))
16943 					return TRUE;
16944 			}
16945 
16946 			return FALSE;
16947 		};
16948 
16949 		function matchAll(names, vars) {
16950 			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
16951 
16952 			// Check start of selection for formats
16953 			startElement = selection.getStart();
16954 			dom.getParent(startElement, function(node) {
16955 				var i, name;
16956 
16957 				for (i = 0; i < names.length; i++) {
16958 					name = names[i];
16959 
16960 					if (!checkedMap[name] && matchNode(node, name, vars)) {
16961 						checkedMap[name] = true;
16962 						matchedFormatNames.push(name);
16963 					}
16964 				}
16965 			}, dom.getRoot());
16966 
16967 			return matchedFormatNames;
16968 		};
16969 
16970 		function canApply(name) {
16971 			var formatList = get(name), startNode, parents, i, x, selector;
16972 
16973 			if (formatList) {
16974 				startNode = selection.getStart();
16975 				parents = getParents(startNode);
16976 
16977 				for (x = formatList.length - 1; x >= 0; x--) {
16978 					selector = formatList[x].selector;
16979 
16980 					// Format is not selector based, then always return TRUE
16981 					if (!selector)
16982 						return TRUE;
16983 
16984 					for (i = parents.length - 1; i >= 0; i--) {
16985 						if (dom.is(parents[i], selector))
16986 							return TRUE;
16987 					}
16988 				}
16989 			}
16990 
16991 			return FALSE;
16992 		};
16993 
16994 		function formatChanged(formats, callback) {
16995 			var currentFormats;
16996 
16997 			// Setup format node change logic
16998 			if (!formatChangeData) {
16999 				formatChangeData = {};
17000 				currentFormats = {};
17001 
17002 				ed.onNodeChange.addToTop(function(ed, cm, node) {
17003 					var parents = getParents(node), matchedFormats = {};
17004 
17005 					// Check for new formats
17006 					each(formatChangeData, function(callbacks, format) {
17007 						each(parents, function(node) {
17008 							if (matchNode(node, format, {}, true)) {
17009 								if (!currentFormats[format]) {
17010 									// Execute callbacks
17011 									each(callbacks, function(callback) {
17012 										callback(true, {node: node, format: format, parents: parents});
17013 									});
17014 
17015 									currentFormats[format] = callbacks;
17016 								}
17017 
17018 								matchedFormats[format] = callbacks;
17019 								return false;
17020 							}
17021 						});
17022 					});
17023 
17024 					// Check if current formats still match
17025 					each(currentFormats, function(callbacks, format) {
17026 						if (!matchedFormats[format]) {
17027 							delete currentFormats[format];
17028 
17029 							each(callbacks, function(callback) {
17030 								callback(false, {node: node, format: format, parents: parents});
17031 							});
17032 						}
17033 					});
17034 				});
17035 			}
17036 
17037 			// Add format listeners
17038 			each(formats.split(','), function(format) {
17039 				if (!formatChangeData[format]) {
17040 					formatChangeData[format] = [];
17041 				}
17042 
17043 				formatChangeData[format].push(callback);
17044 			});
17045 
17046 			return this;
17047 		};
17048 
17049 		// Expose to public
17050 		tinymce.extend(this, {
17051 			get : get,
17052 			register : register,
17053 			apply : apply,
17054 			remove : remove,
17055 			toggle : toggle,
17056 			match : match,
17057 			matchAll : matchAll,
17058 			matchNode : matchNode,
17059 			canApply : canApply,
17060 			formatChanged: formatChanged
17061 		});
17062 
17063 		// Initialize
17064 		defaultFormats();
17065 		addKeyboardShortcuts();
17066 
17067 		// Private functions
17068 
17069 		function matchName(node, format) {
17070 			// Check for inline match
17071 			if (isEq(node, format.inline))
17072 				return TRUE;
17073 
17074 			// Check for block match
17075 			if (isEq(node, format.block))
17076 				return TRUE;
17077 
17078 			// Check for selector match
17079 			if (format.selector)
17080 				return dom.is(node, format.selector);
17081 		};
17082 
17083 		function isEq(str1, str2) {
17084 			str1 = str1 || '';
17085 			str2 = str2 || '';
17086 
17087 			str1 = '' + (str1.nodeName || str1);
17088 			str2 = '' + (str2.nodeName || str2);
17089 
17090 			return str1.toLowerCase() == str2.toLowerCase();
17091 		};
17092 
17093 		function getStyle(node, name) {
17094 			var styleVal = dom.getStyle(node, name);
17095 
17096 			// Force the format to hex
17097 			if (name == 'color' || name == 'backgroundColor')
17098 				styleVal = dom.toHex(styleVal);
17099 
17100 			// Opera will return bold as 700
17101 			if (name == 'fontWeight' && styleVal == 700)
17102 				styleVal = 'bold';
17103 
17104 			return '' + styleVal;
17105 		};
17106 
17107 		function replaceVars(value, vars) {
17108 			if (typeof(value) != "string")
17109 				value = value(vars);
17110 			else if (vars) {
17111 				value = value.replace(/%(\w+)/g, function(str, name) {
17112 					return vars[name] || str;
17113 				});
17114 			}
17115 
17116 			return value;
17117 		};
17118 
17119 		function isWhiteSpaceNode(node) {
17120 			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
17121 		};
17122 
17123 		function wrap(node, name, attrs) {
17124 			var wrapper = dom.create(name, attrs);
17125 
17126 			node.parentNode.insertBefore(wrapper, node);
17127 			wrapper.appendChild(node);
17128 
17129 			return wrapper;
17130 		};
17131 
17132 		function expandRng(rng, format, remove) {
17133 			var sibling, lastIdx, leaf, endPoint,
17134 				startContainer = rng.startContainer,
17135 				startOffset = rng.startOffset,
17136 				endContainer = rng.endContainer,
17137 				endOffset = rng.endOffset;
17138 
17139 			// This function walks up the tree if there is no siblings before/after the node
17140 			function findParentContainer(start) {
17141 				var container, parent, child, sibling, siblingName, root;
17142 
17143 				container = parent = start ? startContainer : endContainer;
17144 				siblingName = start ? 'previousSibling' : 'nextSibling';
17145 				root = dom.getRoot();
17146 
17147 				// If it's a text node and the offset is inside the text
17148 				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
17149 					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
17150 						return container;
17151 					}
17152 				}
17153 
17154 				for (;;) {
17155 					// Stop expanding on block elements
17156 					if (!format[0].block_expand && isBlock(parent))
17157 						return parent;
17158 
17159 					// Walk left/right
17160 					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
17161 						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
17162 							return parent;
17163 						}
17164 					}
17165 
17166 					// Check if we can move up are we at root level or body level
17167 					if (parent.parentNode == root) {
17168 						container = parent;
17169 						break;
17170 					}
17171 
17172 					parent = parent.parentNode;
17173 				}
17174 
17175 				return container;
17176 			};
17177 
17178 			// This function walks down the tree to find the leaf at the selection.
17179 			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
17180 			function findLeaf(node, offset) {
17181 				if (offset === undef)
17182 					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17183 				while (node && node.hasChildNodes()) {
17184 					node = node.childNodes[offset];
17185 					if (node)
17186 						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17187 				}
17188 				return { node: node, offset: offset };
17189 			}
17190 
17191 			// If index based start position then resolve it
17192 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
17193 				lastIdx = startContainer.childNodes.length - 1;
17194 				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
17195 
17196 				if (startContainer.nodeType == 3)
17197 					startOffset = 0;
17198 			}
17199 
17200 			// If index based end position then resolve it
17201 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
17202 				lastIdx = endContainer.childNodes.length - 1;
17203 				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
17204 
17205 				if (endContainer.nodeType == 3)
17206 					endOffset = endContainer.nodeValue.length;
17207 			}
17208 
17209 			// Expands the node to the closes contentEditable false element if it exists
17210 			function findParentContentEditable(node) {
17211 				var parent = node;
17212 
17213 				while (parent) {
17214 					if (parent.nodeType === 1 && getContentEditable(parent)) {
17215 						return getContentEditable(parent) === "false" ? parent : node;
17216 					}
17217 
17218 					parent = parent.parentNode;
17219 				}
17220 
17221 				return node;
17222 			};
17223 
17224 			function findWordEndPoint(container, offset, start) {
17225 				var walker, node, pos, lastTextNode;
17226 
17227 				function findSpace(node, offset) {
17228 					var pos, pos2, str = node.nodeValue;
17229 
17230 					if (typeof(offset) == "undefined") {
17231 						offset = start ? str.length : 0;
17232 					}
17233 
17234 					if (start) {
17235 						pos = str.lastIndexOf(' ', offset);
17236 						pos2 = str.lastIndexOf('\u00a0', offset);
17237 						pos = pos > pos2 ? pos : pos2;
17238 
17239 						// Include the space on remove to avoid tag soup
17240 						if (pos !== -1 && !remove) {
17241 							pos++;
17242 						}
17243 					} else {
17244 						pos = str.indexOf(' ', offset);
17245 						pos2 = str.indexOf('\u00a0', offset);
17246 						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
17247 					}
17248 
17249 					return pos;
17250 				};
17251 
17252 				if (container.nodeType === 3) {
17253 					pos = findSpace(container, offset);
17254 
17255 					if (pos !== -1) {
17256 						return {container : container, offset : pos};
17257 					}
17258 
17259 					lastTextNode = container;
17260 				}
17261 
17262 				// Walk the nodes inside the block
17263 				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
17264 				while (node = walker[start ? 'prev' : 'next']()) {
17265 					if (node.nodeType === 3) {
17266 						lastTextNode = node;
17267 						pos = findSpace(node);
17268 
17269 						if (pos !== -1) {
17270 							return {container : node, offset : pos};
17271 						}
17272 					} else if (isBlock(node)) {
17273 						break;
17274 					}
17275 				}
17276 
17277 				if (lastTextNode) {
17278 					if (start) {
17279 						offset = 0;
17280 					} else {
17281 						offset = lastTextNode.length;
17282 					}
17283 
17284 					return {container: lastTextNode, offset: offset};
17285 				}
17286 			};
17287 
17288 			function findSelectorEndPoint(container, sibling_name) {
17289 				var parents, i, y, curFormat;
17290 
17291 				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
17292 					container = container[sibling_name];
17293 
17294 				parents = getParents(container);
17295 				for (i = 0; i < parents.length; i++) {
17296 					for (y = 0; y < format.length; y++) {
17297 						curFormat = format[y];
17298 
17299 						// If collapsed state is set then skip formats that doesn't match that
17300 						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
17301 							continue;
17302 
17303 						if (dom.is(parents[i], curFormat.selector))
17304 							return parents[i];
17305 					}
17306 				}
17307 
17308 				return container;
17309 			};
17310 
17311 			function findBlockEndPoint(container, sibling_name, sibling_name2) {
17312 				var node;
17313 
17314 				// Expand to block of similar type
17315 				if (!format[0].wrapper)
17316 					node = dom.getParent(container, format[0].block);
17317 
17318 				// Expand to first wrappable block element or any block element
17319 				if (!node)
17320 					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
17321 
17322 				// Exclude inner lists from wrapping
17323 				if (node && format[0].wrapper)
17324 					node = getParents(node, 'ul,ol').reverse()[0] || node;
17325 
17326 				// Didn't find a block element look for first/last wrappable element
17327 				if (!node) {
17328 					node = container;
17329 
17330 					while (node[sibling_name] && !isBlock(node[sibling_name])) {
17331 						node = node[sibling_name];
17332 
17333 						// Break on BR but include it will be removed later on
17334 						// we can't remove it now since we need to check if it can be wrapped
17335 						if (isEq(node, 'br'))
17336 							break;
17337 					}
17338 				}
17339 
17340 				return node || container;
17341 			};
17342 
17343 			// Expand to closest contentEditable element
17344 			startContainer = findParentContentEditable(startContainer);
17345 			endContainer = findParentContentEditable(endContainer);
17346 
17347 			// Exclude bookmark nodes if possible
17348 			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
17349 				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
17350 				startContainer = startContainer.nextSibling || startContainer;
17351 
17352 				if (startContainer.nodeType == 3)
17353 					startOffset = 0;
17354 			}
17355 
17356 			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
17357 				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
17358 				endContainer = endContainer.previousSibling || endContainer;
17359 
17360 				if (endContainer.nodeType == 3)
17361 					endOffset = endContainer.length;
17362 			}
17363 
17364 			if (format[0].inline) {
17365 				if (rng.collapsed) {
17366 					// Expand left to closest word boundery
17367 					endPoint = findWordEndPoint(startContainer, startOffset, true);
17368 					if (endPoint) {
17369 						startContainer = endPoint.container;
17370 						startOffset = endPoint.offset;
17371 					}
17372 
17373 					// Expand right to closest word boundery
17374 					endPoint = findWordEndPoint(endContainer, endOffset);
17375 					if (endPoint) {
17376 						endContainer = endPoint.container;
17377 						endOffset = endPoint.offset;
17378 					}
17379 				}
17380 
17381 				// Avoid applying formatting to a trailing space.
17382 				leaf = findLeaf(endContainer, endOffset);
17383 				if (leaf.node) {
17384 					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
17385 						leaf = findLeaf(leaf.node.previousSibling);
17386 
17387 					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
17388 							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
17389 
17390 						if (leaf.offset > 1) {
17391 							endContainer = leaf.node;
17392 							endContainer.splitText(leaf.offset - 1);
17393 						}
17394 					}
17395 				}
17396 			}
17397 
17398 			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
17399 			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
17400 			// This will reduce the number of wrapper elements that needs to be created
17401 			// Move start point up the tree
17402 			if (format[0].inline || format[0].block_expand) {
17403 				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
17404 					startContainer = findParentContainer(true);
17405 				}
17406 
17407 				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
17408 					endContainer = findParentContainer();
17409 				}
17410 			}
17411 
17412 			// Expand start/end container to matching selector
17413 			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
17414 				// Find new startContainer/endContainer if there is better one
17415 				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
17416 				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
17417 			}
17418 
17419 			// Expand start/end container to matching block element or text node
17420 			if (format[0].block || format[0].selector) {
17421 				// Find new startContainer/endContainer if there is better one
17422 				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
17423 				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
17424 
17425 				// Non block element then try to expand up the leaf
17426 				if (format[0].block) {
17427 					if (!isBlock(startContainer))
17428 						startContainer = findParentContainer(true);
17429 
17430 					if (!isBlock(endContainer))
17431 						endContainer = findParentContainer();
17432 				}
17433 			}
17434 
17435 			// Setup index for startContainer
17436 			if (startContainer.nodeType == 1) {
17437 				startOffset = nodeIndex(startContainer);
17438 				startContainer = startContainer.parentNode;
17439 			}
17440 
17441 			// Setup index for endContainer
17442 			if (endContainer.nodeType == 1) {
17443 				endOffset = nodeIndex(endContainer) + 1;
17444 				endContainer = endContainer.parentNode;
17445 			}
17446 
17447 			// Return new range like object
17448 			return {
17449 				startContainer : startContainer,
17450 				startOffset : startOffset,
17451 				endContainer : endContainer,
17452 				endOffset : endOffset
17453 			};
17454 		}
17455 
17456 		function removeFormat(format, vars, node, compare_node) {
17457 			var i, attrs, stylesModified;
17458 
17459 			// Check if node matches format
17460 			if (!matchName(node, format))
17461 				return FALSE;
17462 
17463 			// Should we compare with format attribs and styles
17464 			if (format.remove != 'all') {
17465 				// Remove styles
17466 				each(format.styles, function(value, name) {
17467 					value = replaceVars(value, vars);
17468 
17469 					// Indexed array
17470 					if (typeof(name) === 'number') {
17471 						name = value;
17472 						compare_node = 0;
17473 					}
17474 
17475 					if (!compare_node || isEq(getStyle(compare_node, name), value))
17476 						dom.setStyle(node, name, '');
17477 
17478 					stylesModified = 1;
17479 				});
17480 
17481 				// Remove style attribute if it's empty
17482 				if (stylesModified && dom.getAttrib(node, 'style') == '') {
17483 					node.removeAttribute('style');
17484 					node.removeAttribute('data-mce-style');
17485 				}
17486 
17487 				// Remove attributes
17488 				each(format.attributes, function(value, name) {
17489 					var valueOut;
17490 
17491 					value = replaceVars(value, vars);
17492 
17493 					// Indexed array
17494 					if (typeof(name) === 'number') {
17495 						name = value;
17496 						compare_node = 0;
17497 					}
17498 
17499 					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
17500 						// Keep internal classes
17501 						if (name == 'class') {
17502 							value = dom.getAttrib(node, name);
17503 							if (value) {
17504 								// Build new class value where everything is removed except the internal prefixed classes
17505 								valueOut = '';
17506 								each(value.split(/\s+/), function(cls) {
17507 									if (/mce\w+/.test(cls))
17508 										valueOut += (valueOut ? ' ' : '') + cls;
17509 								});
17510 
17511 								// We got some internal classes left
17512 								if (valueOut) {
17513 									dom.setAttrib(node, name, valueOut);
17514 									return;
17515 								}
17516 							}
17517 						}
17518 
17519 						// IE6 has a bug where the attribute doesn't get removed correctly
17520 						if (name == "class")
17521 							node.removeAttribute('className');
17522 
17523 						// Remove mce prefixed attributes
17524 						if (MCE_ATTR_RE.test(name))
17525 							node.removeAttribute('data-mce-' + name);
17526 
17527 						node.removeAttribute(name);
17528 					}
17529 				});
17530 
17531 				// Remove classes
17532 				each(format.classes, function(value) {
17533 					value = replaceVars(value, vars);
17534 
17535 					if (!compare_node || dom.hasClass(compare_node, value))
17536 						dom.removeClass(node, value);
17537 				});
17538 
17539 				// Check for non internal attributes
17540 				attrs = dom.getAttribs(node);
17541 				for (i = 0; i < attrs.length; i++) {
17542 					if (attrs[i].nodeName.indexOf('_') !== 0)
17543 						return FALSE;
17544 				}
17545 			}
17546 
17547 			// Remove the inline child if it's empty for example <b> or <span>
17548 			if (format.remove != 'none') {
17549 				removeNode(node, format);
17550 				return TRUE;
17551 			}
17552 		};
17553 
17554 		function removeNode(node, format) {
17555 			var parentNode = node.parentNode, rootBlockElm;
17556 
17557 			function find(node, next, inc) {
17558 				node = getNonWhiteSpaceSibling(node, next, inc);
17559 
17560 				return !node || (node.nodeName == 'BR' || isBlock(node));
17561 			};
17562 
17563 			if (format.block) {
17564 				if (!forcedRootBlock) {
17565 					// Append BR elements if needed before we remove the block
17566 					if (isBlock(node) && !isBlock(parentNode)) {
17567 						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
17568 							node.insertBefore(dom.create('br'), node.firstChild);
17569 
17570 						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
17571 							node.appendChild(dom.create('br'));
17572 					}
17573 				} else {
17574 					// Wrap the block in a forcedRootBlock if we are at the root of document
17575 					if (parentNode == dom.getRoot()) {
17576 						if (!format.list_block || !isEq(node, format.list_block)) {
17577 							each(tinymce.grep(node.childNodes), function(node) {
17578 								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
17579 									if (!rootBlockElm)
17580 										rootBlockElm = wrap(node, forcedRootBlock);
17581 									else
17582 										rootBlockElm.appendChild(node);
17583 								} else
17584 									rootBlockElm = 0;
17585 							});
17586 						}
17587 					}
17588 				}
17589 			}
17590 
17591 			// Never remove nodes that isn't the specified inline element if a selector is specified too
17592 			if (format.selector && format.inline && !isEq(format.inline, node))
17593 				return;
17594 
17595 			dom.remove(node, 1);
17596 		};
17597 
17598 		function getNonWhiteSpaceSibling(node, next, inc) {
17599 			if (node) {
17600 				next = next ? 'nextSibling' : 'previousSibling';
17601 
17602 				for (node = inc ? node : node[next]; node; node = node[next]) {
17603 					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
17604 						return node;
17605 				}
17606 			}
17607 		};
17608 
17609 		function isBookmarkNode(node) {
17610 			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
17611 		};
17612 
17613 		function mergeSiblings(prev, next) {
17614 			var marker, sibling, tmpSibling;
17615 
17616 			function compareElements(node1, node2) {
17617 				// Not the same name
17618 				if (node1.nodeName != node2.nodeName)
17619 					return FALSE;
17620 
17621 				function getAttribs(node) {
17622 					var attribs = {};
17623 
17624 					each(dom.getAttribs(node), function(attr) {
17625 						var name = attr.nodeName.toLowerCase();
17626 
17627 						// Don't compare internal attributes or style
17628 						if (name.indexOf('_') !== 0 && name !== 'style')
17629 							attribs[name] = dom.getAttrib(node, name);
17630 					});
17631 
17632 					return attribs;
17633 				};
17634 
17635 				function compareObjects(obj1, obj2) {
17636 					var value, name;
17637 
17638 					for (name in obj1) {
17639 						// Obj1 has item obj2 doesn't have
17640 						if (obj1.hasOwnProperty(name)) {
17641 							value = obj2[name];
17642 
17643 							// Obj2 doesn't have obj1 item
17644 							if (value === undef)
17645 								return FALSE;
17646 
17647 							// Obj2 item has a different value
17648 							if (obj1[name] != value)
17649 								return FALSE;
17650 
17651 							// Delete similar value
17652 							delete obj2[name];
17653 						}
17654 					}
17655 
17656 					// Check if obj 2 has something obj 1 doesn't have
17657 					for (name in obj2) {
17658 						// Obj2 has item obj1 doesn't have
17659 						if (obj2.hasOwnProperty(name))
17660 							return FALSE;
17661 					}
17662 
17663 					return TRUE;
17664 				};
17665 
17666 				// Attribs are not the same
17667 				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
17668 					return FALSE;
17669 
17670 				// Styles are not the same
17671 				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
17672 					return FALSE;
17673 
17674 				return TRUE;
17675 			};
17676 
17677 			function findElementSibling(node, sibling_name) {
17678 				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
17679 					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
17680 						return node;
17681 
17682 					if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
17683 						return sibling;
17684 				}
17685 
17686 				return node;
17687 			};
17688 
17689 			// Check if next/prev exists and that they are elements
17690 			if (prev && next) {
17691 				// If previous sibling is empty then jump over it
17692 				prev = findElementSibling(prev, 'previousSibling');
17693 				next = findElementSibling(next, 'nextSibling');
17694 
17695 				// Compare next and previous nodes
17696 				if (compareElements(prev, next)) {
17697 					// Append nodes between
17698 					for (sibling = prev.nextSibling; sibling && sibling != next;) {
17699 						tmpSibling = sibling;
17700 						sibling = sibling.nextSibling;
17701 						prev.appendChild(tmpSibling);
17702 					}
17703 
17704 					// Remove next node
17705 					dom.remove(next);
17706 
17707 					// Move children into prev node
17708 					each(tinymce.grep(next.childNodes), function(node) {
17709 						prev.appendChild(node);
17710 					});
17711 
17712 					return prev;
17713 				}
17714 			}
17715 
17716 			return next;
17717 		};
17718 
17719 		function isTextBlock(name) {
17720 			return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
17721 		};
17722 
17723 		function getContainer(rng, start) {
17724 			var container, offset, lastIdx, walker;
17725 
17726 			container = rng[start ? 'startContainer' : 'endContainer'];
17727 			offset = rng[start ? 'startOffset' : 'endOffset'];
17728 
17729 			if (container.nodeType == 1) {
17730 				lastIdx = container.childNodes.length - 1;
17731 
17732 				if (!start && offset)
17733 					offset--;
17734 
17735 				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
17736 			}
17737 
17738 			// If start text node is excluded then walk to the next node
17739 			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
17740 				container = new TreeWalker(container, ed.getBody()).next() || container;
17741 			}
17742 
17743 			// If end text node is excluded then walk to the previous node
17744 			if (container.nodeType === 3 && !start && offset === 0) {
17745 				container = new TreeWalker(container, ed.getBody()).prev() || container;
17746 			}
17747 
17748 			return container;
17749 		};
17750 
17751 		function performCaretAction(type, name, vars) {
17752 			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
17753 
17754 			// Creates a caret container bogus element
17755 			function createCaretContainer(fill) {
17756 				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
17757 
17758 				if (fill) {
17759 					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
17760 				}
17761 
17762 				return caretContainer;
17763 			};
17764 
17765 			function isCaretContainerEmpty(node, nodes) {
17766 				while (node) {
17767 					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
17768 						return false;
17769 					}
17770 
17771 					// Collect nodes
17772 					if (nodes && node.nodeType === 1) {
17773 						nodes.push(node);
17774 					}
17775 
17776 					node = node.firstChild;
17777 				}
17778 
17779 				return true;
17780 			};
17781 			
17782 			// Returns any parent caret container element
17783 			function getParentCaretContainer(node) {
17784 				while (node) {
17785 					if (node.id === caretContainerId) {
17786 						return node;
17787 					}
17788 
17789 					node = node.parentNode;
17790 				}
17791 			};
17792 
17793 			// Finds the first text node in the specified node
17794 			function findFirstTextNode(node) {
17795 				var walker;
17796 
17797 				if (node) {
17798 					walker = new TreeWalker(node, node);
17799 
17800 					for (node = walker.current(); node; node = walker.next()) {
17801 						if (node.nodeType === 3) {
17802 							return node;
17803 						}
17804 					}
17805 				}
17806 			};
17807 
17808 			// Removes the caret container for the specified node or all on the current document
17809 			function removeCaretContainer(node, move_caret) {
17810 				var child, rng;
17811 
17812 				if (!node) {
17813 					node = getParentCaretContainer(selection.getStart());
17814 
17815 					if (!node) {
17816 						while (node = dom.get(caretContainerId)) {
17817 							removeCaretContainer(node, false);
17818 						}
17819 					}
17820 				} else {
17821 					rng = selection.getRng(true);
17822 
17823 					if (isCaretContainerEmpty(node)) {
17824 						if (move_caret !== false) {
17825 							rng.setStartBefore(node);
17826 							rng.setEndBefore(node);
17827 						}
17828 
17829 						dom.remove(node);
17830 					} else {
17831 						child = findFirstTextNode(node);
17832 
17833 						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
17834 							child = child.deleteData(0, 1);
17835 						}
17836 
17837 						dom.remove(node, 1);
17838 					}
17839 
17840 					selection.setRng(rng);
17841 				}
17842 			};
17843 			
17844 			// Applies formatting to the caret postion
17845 			function applyCaretFormat() {
17846 				var rng, caretContainer, textNode, offset, bookmark, container, text;
17847 
17848 				rng = selection.getRng(true);
17849 				offset = rng.startOffset;
17850 				container = rng.startContainer;
17851 				text = container.nodeValue;
17852 
17853 				caretContainer = getParentCaretContainer(selection.getStart());
17854 				if (caretContainer) {
17855 					textNode = findFirstTextNode(caretContainer);
17856 				}
17857 
17858 				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
17859 				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
17860 					// Get bookmark of caret position
17861 					bookmark = selection.getBookmark();
17862 
17863 					// Collapse bookmark range (WebKit)
17864 					rng.collapse(true);
17865 
17866 					// Expand the range to the closest word and split it at those points
17867 					rng = expandRng(rng, get(name));
17868 					rng = rangeUtils.split(rng);
17869 
17870 					// Apply the format to the range
17871 					apply(name, vars, rng);
17872 
17873 					// Move selection back to caret position
17874 					selection.moveToBookmark(bookmark);
17875 				} else {
17876 					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
17877 						caretContainer = createCaretContainer(true);
17878 						textNode = caretContainer.firstChild;
17879 
17880 						rng.insertNode(caretContainer);
17881 						offset = 1;
17882 
17883 						apply(name, vars, caretContainer);
17884 					} else {
17885 						apply(name, vars, caretContainer);
17886 					}
17887 
17888 					// Move selection to text node
17889 					selection.setCursorLocation(textNode, offset);
17890 				}
17891 			};
17892 
17893 			function removeCaretFormat() {
17894 				var rng = selection.getRng(true), container, offset, bookmark,
17895 					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
17896 
17897 				container = rng.startContainer;
17898 				offset = rng.startOffset;
17899 				node = container;
17900 
17901 				if (container.nodeType == 3) {
17902 					if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
17903 						hasContentAfter = true;
17904 					}
17905 
17906 					node = node.parentNode;
17907 				}
17908 
17909 				while (node) {
17910 					if (matchNode(node, name, vars)) {
17911 						formatNode = node;
17912 						break;
17913 					}
17914 
17915 					if (node.nextSibling) {
17916 						hasContentAfter = true;
17917 					}
17918 
17919 					parents.push(node);
17920 					node = node.parentNode;
17921 				}
17922 
17923 				// Node doesn't have the specified format
17924 				if (!formatNode) {
17925 					return;
17926 				}
17927 
17928 				// Is there contents after the caret then remove the format on the element
17929 				if (hasContentAfter) {
17930 					// Get bookmark of caret position
17931 					bookmark = selection.getBookmark();
17932 
17933 					// Collapse bookmark range (WebKit)
17934 					rng.collapse(true);
17935 
17936 					// Expand the range to the closest word and split it at those points
17937 					rng = expandRng(rng, get(name), true);
17938 					rng = rangeUtils.split(rng);
17939 
17940 					// Remove the format from the range
17941 					remove(name, vars, rng);
17942 
17943 					// Move selection back to caret position
17944 					selection.moveToBookmark(bookmark);
17945 				} else {
17946 					caretContainer = createCaretContainer();
17947 
17948 					node = caretContainer;
17949 					for (i = parents.length - 1; i >= 0; i--) {
17950 						node.appendChild(dom.clone(parents[i], false));
17951 						node = node.firstChild;
17952 					}
17953 
17954 					// Insert invisible character into inner most format element
17955 					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
17956 					node = node.firstChild;
17957 
17958 					// Insert caret container after the formated node
17959 					dom.insertAfter(caretContainer, formatNode);
17960 
17961 					// Move selection to text node
17962 					selection.setCursorLocation(node, 1);
17963 				}
17964 			};
17965 
17966 			// Checks if the parent caret container node isn't empty if that is the case it
17967 			// will remove the bogus state on all children that isn't empty
17968 			function unmarkBogusCaretParents() {
17969 				var i, caretContainer, node;
17970 
17971 				caretContainer = getParentCaretContainer(selection.getStart());
17972 				if (caretContainer && !dom.isEmpty(caretContainer)) {
17973 					tinymce.walk(caretContainer, function(node) {
17974 						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
17975 							dom.setAttrib(node, 'data-mce-bogus', null);
17976 						}
17977 					}, 'childNodes');
17978 				}
17979 			};
17980 
17981 			// Only bind the caret events once
17982 			if (!self._hasCaretEvents) {
17983 				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
17984 				ed.onBeforeGetContent.addToTop(function() {
17985 					var nodes = [], i;
17986 
17987 					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
17988 						// Mark children
17989 						i = nodes.length;
17990 						while (i--) {
17991 							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
17992 						}
17993 					}
17994 				});
17995 
17996 				// Remove caret container on mouse up and on key up
17997 				tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
17998 					ed[name].addToTop(function() {
17999 						removeCaretContainer();
18000 						unmarkBogusCaretParents();
18001 					});
18002 				});
18003 
18004 				// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
18005 				ed.onKeyDown.addToTop(function(ed, e) {
18006 					var keyCode = e.keyCode;
18007 
18008 					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
18009 						removeCaretContainer(getParentCaretContainer(selection.getStart()));
18010 					}
18011 
18012 					unmarkBogusCaretParents();
18013 				});
18014 
18015 				// Remove bogus state if they got filled by contents using editor.selection.setContent
18016 				selection.onSetContent.add(unmarkBogusCaretParents);
18017 
18018 				self._hasCaretEvents = true;
18019 			}
18020 
18021 			// Do apply or remove caret format
18022 			if (type == "apply") {
18023 				applyCaretFormat();
18024 			} else {
18025 				removeCaretFormat();
18026 			}
18027 		};
18028 
18029 		function moveStart(rng) {
18030 			var container = rng.startContainer,
18031 					offset = rng.startOffset, isAtEndOfText,
18032 					walker, node, nodes, tmpNode;
18033 
18034 			// Convert text node into index if possible
18035 			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
18036 				// Get the parent container location and walk from there
18037 				offset = nodeIndex(container);
18038 				container = container.parentNode;
18039 				isAtEndOfText = true;
18040 			}
18041 
18042 			// Move startContainer/startOffset in to a suitable node
18043 			if (container.nodeType == 1) {
18044 				nodes = container.childNodes;
18045 				container = nodes[Math.min(offset, nodes.length - 1)];
18046 				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
18047 
18048 				// If offset is at end of the parent node walk to the next one
18049 				if (offset > nodes.length - 1 || isAtEndOfText)
18050 					walker.next();
18051 
18052 				for (node = walker.current(); node; node = walker.next()) {
18053 					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
18054 						// IE has a "neat" feature where it moves the start node into the closest element
18055 						// we can avoid this by inserting an element before it and then remove it after we set the selection
18056 						tmpNode = dom.create('a', null, INVISIBLE_CHAR);
18057 						node.parentNode.insertBefore(tmpNode, node);
18058 
18059 						// Set selection and remove tmpNode
18060 						rng.setStart(node, 0);
18061 						selection.setRng(rng);
18062 						dom.remove(tmpNode);
18063 
18064 						return;
18065 					}
18066 				}
18067 			}
18068 		};
18069 	};
18070 })(tinymce);
18071 
18072 tinymce.onAddEditor.add(function(tinymce, ed) {
18073 	var filters, fontSizes, dom, settings = ed.settings;
18074 
18075 	function replaceWithSpan(node, styles) {
18076 		tinymce.each(styles, function(value, name) {
18077 			if (value)
18078 				dom.setStyle(node, name, value);
18079 		});
18080 
18081 		dom.rename(node, 'span');
18082 	};
18083 
18084 	function convert(editor, params) {
18085 		dom = editor.dom;
18086 
18087 		if (settings.convert_fonts_to_spans) {
18088 			tinymce.each(dom.select('font,u,strike', params.node), function(node) {
18089 				filters[node.nodeName.toLowerCase()](ed.dom, node);
18090 			});
18091 		}
18092 	};
18093 
18094 	if (settings.inline_styles) {
18095 		fontSizes = tinymce.explode(settings.font_size_legacy_values);
18096 
18097 		filters = {
18098 			font : function(dom, node) {
18099 				replaceWithSpan(node, {
18100 					backgroundColor : node.style.backgroundColor,
18101 					color : node.color,
18102 					fontFamily : node.face,
18103 					fontSize : fontSizes[parseInt(node.size, 10) - 1]
18104 				});
18105 			},
18106 
18107 			u : function(dom, node) {
18108 				replaceWithSpan(node, {
18109 					textDecoration : 'underline'
18110 				});
18111 			},
18112 
18113 			strike : function(dom, node) {
18114 				replaceWithSpan(node, {
18115 					textDecoration : 'line-through'
18116 				});
18117 			}
18118 		};
18119 
18120 		ed.onPreProcess.add(convert);
18121 		ed.onSetContent.add(convert);
18122 
18123 		ed.onInit.add(function() {
18124 			ed.selection.onSetContent.add(convert);
18125 		});
18126 	}
18127 });
18128 
18129 (function(tinymce) {
18130 	var TreeWalker = tinymce.dom.TreeWalker;
18131 
18132 	tinymce.EnterKey = function(editor) {
18133 		var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18134 
18135 		function handleEnterKey(evt) {
18136 			var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
18137 				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
18138 
18139 			// Returns true if the block can be split into two blocks or not
18140 			function canSplitBlock(node) {
18141 				return node &&
18142 					dom.isBlock(node) &&
18143 					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
18144 					!/^(fixed|absolute)/i.test(node.style.position) && 
18145 					dom.getContentEditable(node) !== "true";
18146 			};
18147 
18148 			// Renders empty block on IE
18149 			function renderBlockOnIE(block) {
18150 				var oldRng;
18151 
18152 				if (tinymce.isIE && dom.isBlock(block)) {
18153 					oldRng = selection.getRng();
18154 					block.appendChild(dom.create('span', null, '\u00a0'));
18155 					selection.select(block);
18156 					block.lastChild.outerHTML = '';
18157 					selection.setRng(oldRng);
18158 				}
18159 			};
18160 
18161 			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
18162 			function trimInlineElementsOnLeftSideOfBlock(block) {
18163 				var node = block, firstChilds = [], i;
18164 
18165 				// Find inner most first child ex: <p><i><b>*</b></i></p>
18166 				while (node = node.firstChild) {
18167 					if (dom.isBlock(node)) {
18168 						return;
18169 					}
18170 
18171 					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18172 						firstChilds.push(node);
18173 					}
18174 				}
18175 
18176 				i = firstChilds.length;
18177 				while (i--) {
18178 					node = firstChilds[i];
18179 					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
18180 						dom.remove(node);
18181 					}
18182 				}
18183 			};
18184 			
18185 			// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
18186 			function moveToCaretPosition(root) {
18187 				var walker, node, rng, y, viewPort, lastNode = root, tempElm;
18188 
18189 				rng = dom.createRng();
18190 
18191 				if (root.hasChildNodes()) {
18192 					walker = new TreeWalker(root, root);
18193 
18194 					while (node = walker.current()) {
18195 						if (node.nodeType == 3) {
18196 							rng.setStart(node, 0);
18197 							rng.setEnd(node, 0);
18198 							break;
18199 						}
18200 
18201 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18202 							rng.setStartBefore(node);
18203 							rng.setEndBefore(node);
18204 							break;
18205 						}
18206 
18207 						lastNode = node;
18208 						node = walker.next();
18209 					}
18210 
18211 					if (!node) {
18212 						rng.setStart(lastNode, 0);
18213 						rng.setEnd(lastNode, 0);
18214 					}
18215 				} else {
18216 					if (root.nodeName == 'BR') {
18217 						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
18218 							// Trick on older IE versions to render the caret before the BR between two lists
18219 							if (!documentMode || documentMode < 9) {
18220 								tempElm = dom.create('br');
18221 								root.parentNode.insertBefore(tempElm, root);
18222 							}
18223 
18224 							rng.setStartBefore(root);
18225 							rng.setEndBefore(root);
18226 						} else {
18227 							rng.setStartAfter(root);
18228 							rng.setEndAfter(root);
18229 						}
18230 					} else {
18231 						rng.setStart(root, 0);
18232 						rng.setEnd(root, 0);
18233 					}
18234 				}
18235 
18236 				selection.setRng(rng);
18237 
18238 				// Remove tempElm created for old IE:s
18239 				dom.remove(tempElm);
18240 
18241 				viewPort = dom.getViewPort(editor.getWin());
18242 
18243 				// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
18244 				y = dom.getPos(root).y;
18245 				if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
18246 					editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
18247 				}
18248 			};
18249 
18250 			// Creates a new block element by cloning the current one or creating a new one if the name is specified
18251 			// This function will also copy any text formatting from the parent block and add it to the new one
18252 			function createNewBlock(name) {
18253 				var node = container, block, clonedNode, caretNode;
18254 
18255 				block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
18256 				caretNode = block;
18257 
18258 				// Clone any parent styles
18259 				if (settings.keep_styles !== false) {
18260 					do {
18261 						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
18262 							clonedNode = node.cloneNode(false);
18263 							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
18264 
18265 							if (block.hasChildNodes()) {
18266 								clonedNode.appendChild(block.firstChild);
18267 								block.appendChild(clonedNode);
18268 							} else {
18269 								caretNode = clonedNode;
18270 								block.appendChild(clonedNode);
18271 							}
18272 						}
18273 					} while (node = node.parentNode);
18274 				}
18275 
18276 				// BR is needed in empty blocks on non IE browsers
18277 				if (!tinymce.isIE) {
18278 					caretNode.innerHTML = '<br>';
18279 				}
18280 
18281 				return block;
18282 			};
18283 
18284 			// Returns true/false if the caret is at the start/end of the parent block element
18285 			function isCaretAtStartOrEndOfBlock(start) {
18286 				var walker, node, name;
18287 
18288 				// Caret is in the middle of a text node like "a|b"
18289 				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
18290 					return false;
18291 				}
18292 
18293 				// If after the last element in block node edge case for #5091
18294 				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
18295 					return true;
18296 				}
18297 
18298 				// If the caret if before the first element in parentBlock
18299 				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
18300 					return true;
18301 				}
18302 
18303 				// Caret can be before/after a table
18304 				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
18305 					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
18306 				}
18307 
18308 				// Walk the DOM and look for text nodes or non empty elements
18309 				walker = new TreeWalker(container, parentBlock);
18310 	
18311 				// If caret is in beginning or end of a text block then jump to the next/previous node
18312 				if (container.nodeType == 3) {
18313 					if (start && offset == 0) {
18314 						walker.prev();
18315 					} else if (!start && offset == container.nodeValue.length) {
18316 						walker.next();
18317 					}
18318 				}
18319 
18320 				while (node = walker.current()) {
18321 					if (node.nodeType === 1) {
18322 						// Ignore bogus elements
18323 						if (!node.getAttribute('data-mce-bogus')) {
18324 							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
18325 							name = node.nodeName.toLowerCase();
18326 							if (nonEmptyElementsMap[name] && name !== 'br') {
18327 								return false;
18328 							}
18329 						}
18330 					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
18331 						return false;
18332 					}
18333 
18334 					if (start) {
18335 						walker.prev();
18336 					} else {
18337 						walker.next();
18338 					}
18339 				}
18340 
18341 				return true;
18342 			};
18343 
18344 			// Wraps any text nodes or inline elements in the specified forced root block name
18345 			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
18346 				var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
18347 
18348 				// Not in a block element or in a table cell or caption
18349 				parentBlock = dom.getParent(container, dom.isBlock);
18350 				if (!parentBlock || !canSplitBlock(parentBlock)) {
18351 					parentBlock = parentBlock || editableRoot;
18352 
18353 					if (!parentBlock.hasChildNodes()) {
18354 						newBlock = dom.create(blockName);
18355 						parentBlock.appendChild(newBlock);
18356 						rng.setStart(newBlock, 0);
18357 						rng.setEnd(newBlock, 0);
18358 						return newBlock;
18359 					}
18360 
18361 					// Find parent that is the first child of parentBlock
18362 					node = container;
18363 					while (node.parentNode != parentBlock) {
18364 						node = node.parentNode;
18365 					}
18366 
18367 					// Loop left to find start node start wrapping at
18368 					while (node && !dom.isBlock(node)) {
18369 						startNode = node;
18370 						node = node.previousSibling;
18371 					}
18372 
18373 					if (startNode) {
18374 						newBlock = dom.create(blockName);
18375 						startNode.parentNode.insertBefore(newBlock, startNode);
18376 
18377 						// Start wrapping until we hit a block
18378 						node = startNode;
18379 						while (node && !dom.isBlock(node)) {
18380 							next = node.nextSibling;
18381 							newBlock.appendChild(node);
18382 							node = next;
18383 						}
18384 
18385 						// Restore range to it's past location
18386 						rng.setStart(container, offset);
18387 						rng.setEnd(container, offset);
18388 					}
18389 				}
18390 
18391 				return container;
18392 			};
18393 
18394 			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
18395 			function handleEmptyListItem() {
18396 				function isFirstOrLastLi(first) {
18397 					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
18398 
18399 					// Find first/last element since there might be whitespace there
18400 					while (node) {
18401 						if (node.nodeType == 1) {
18402 							break;
18403 						}
18404 
18405 						node = node[first ? 'nextSibling' : 'previousSibling'];
18406 					}
18407 
18408 					return node === parentBlock;
18409 				};
18410 
18411 				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
18412 
18413 				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
18414 					// Is first and last list item then replace the OL/UL with a text block
18415 					dom.replace(newBlock, containerBlock);
18416 				} else if (isFirstOrLastLi(true)) {
18417 					// First LI in list then remove LI and add text block before list
18418 					containerBlock.parentNode.insertBefore(newBlock, containerBlock);
18419 				} else if (isFirstOrLastLi()) {
18420 					// Last LI in list then temove LI and add text block after list
18421 					dom.insertAfter(newBlock, containerBlock);
18422 					renderBlockOnIE(newBlock);
18423 				} else {
18424 					// Middle LI in list the split the list and insert a text block in the middle
18425 					// Extract after fragment and insert it after the current block
18426 					tmpRng = rng.cloneRange();
18427 					tmpRng.setStartAfter(parentBlock);
18428 					tmpRng.setEndAfter(containerBlock);
18429 					fragment = tmpRng.extractContents();
18430 					dom.insertAfter(fragment, containerBlock);
18431 					dom.insertAfter(newBlock, containerBlock);
18432 				}
18433 
18434 				dom.remove(parentBlock);
18435 				moveToCaretPosition(newBlock);
18436 				undoManager.add();
18437 			};
18438 
18439 			// Walks the parent block to the right and look for BR elements
18440 			function hasRightSideBr() {
18441 				var walker = new TreeWalker(container, parentBlock), node;
18442 
18443 				while (node = walker.current()) {
18444 					if (node.nodeName == 'BR') {
18445 						return true;
18446 					}
18447 
18448 					node = walker.next();
18449 				}
18450 			}
18451 			
18452 			// Inserts a BR element if the forced_root_block option is set to false or empty string
18453 			function insertBr() {
18454 				var brElm, extraBr;
18455 
18456 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18457 					// Insert extra BR element at the end block elements
18458 					if (!tinymce.isIE && !hasRightSideBr()) {
18459 						brElm = dom.create('br')
18460 						rng.insertNode(brElm);
18461 						rng.setStartAfter(brElm);
18462 						rng.setEndAfter(brElm);
18463 						extraBr = true;
18464 					}
18465 				}
18466 
18467 				brElm = dom.create('br');
18468 				rng.insertNode(brElm);
18469 
18470 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18471 				if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18472 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18473 				}
18474 
18475 				if (!extraBr) {
18476 					rng.setStartAfter(brElm);
18477 					rng.setEndAfter(brElm);
18478 				} else {
18479 					rng.setStartBefore(brElm);
18480 					rng.setEndBefore(brElm);
18481 				}
18482 
18483 				selection.setRng(rng);
18484 				undoManager.add();
18485 			};
18486 
18487 			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
18488 			function trimLeadingLineBreaks(node) {
18489 				do {
18490 					if (node.nodeType === 3) {
18491 						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
18492 					}
18493 
18494 					node = node.firstChild;
18495 				} while (node);
18496 			};
18497 
18498 			function getEditableRoot(node) {
18499 				var root = dom.getRoot(), parent, editableRoot;
18500 
18501 				// Get all parents until we hit a non editable parent or the root
18502 				parent = node;
18503 				while (parent !== root && dom.getContentEditable(parent) !== "false") {
18504 					if (dom.getContentEditable(parent) === "true") {
18505 						editableRoot = parent;
18506 					}
18507 
18508 					parent = parent.parentNode;
18509 				}
18510 				
18511 				return parent !== root ? editableRoot : root;
18512 			};
18513 
18514 			// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
18515 			function addBrToBlockIfNeeded(block) {
18516 				var lastChild;
18517 
18518 				// IE will render the blocks correctly other browsers needs a BR
18519 				if (!tinymce.isIE) {
18520 					block.normalize(); // Remove empty text nodes that got left behind by the extract
18521 
18522 					// Check if the block is empty or contains a floated last child
18523 					lastChild = block.lastChild;
18524 					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
18525 						dom.add(block, 'br');
18526 					}
18527 				}
18528 			};
18529 
18530 			// Delete any selected contents
18531 			if (!rng.collapsed) {
18532 				editor.execCommand('Delete');
18533 				return;
18534 			}
18535 
18536 			// Event is blocked by some other handler for example the lists plugin
18537 			if (evt.isDefaultPrevented()) {
18538 				return;
18539 			}
18540 
18541 			// Setup range items and newBlockName
18542 			container = rng.startContainer;
18543 			offset = rng.startOffset;
18544 			newBlockName = settings.forced_root_block;
18545 			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
18546 			documentMode = dom.doc.documentMode;
18547 
18548 			// Resolve node index
18549 			if (container.nodeType == 1 && container.hasChildNodes()) {
18550 				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18551 				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18552 				if (isAfterLastNodeInContainer && container.nodeType == 3) {
18553 					offset = container.nodeValue.length;
18554 				} else {
18555 					offset = 0;
18556 				}
18557 			}
18558 
18559 			// Get editable root node normaly the body element but sometimes a div or span
18560 			editableRoot = getEditableRoot(container);
18561 
18562 			// If there is no editable root then enter is done inside a contentEditable false element
18563 			if (!editableRoot) {
18564 				return;
18565 			}
18566 
18567 			undoManager.beforeChange();
18568 
18569 			// If editable root isn't block nor the root of the editor
18570 			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
18571 				if (!newBlockName || evt.shiftKey) {
18572 					insertBr();
18573 				}
18574 
18575 				return;
18576 			}
18577 
18578 			// Wrap the current node and it's sibling in a default block if it's needed.
18579 			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
18580 			// This won't happen if root blocks are disabled or the shiftKey is pressed
18581 			if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
18582 				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
18583 			}
18584 
18585 			// Find parent block and setup empty block paddings
18586 			parentBlock = dom.getParent(container, dom.isBlock);
18587 			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
18588 
18589 			// Setup block names
18590 			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18591 			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18592 
18593 			// Handle enter inside an empty list item
18594 			if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
18595 				// Let the list plugin or browser handle nested lists for now
18596 				if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
18597 					return false;
18598 				}
18599 
18600 				handleEmptyListItem();
18601 				return;
18602 			}
18603 
18604 			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
18605 			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
18606 				if (!evt.shiftKey) {
18607 					insertBr();
18608 					return;
18609 				}
18610 			} else {
18611 				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
18612 				if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
18613 					insertBr();
18614 					return;
18615 				}
18616 			}
18617 
18618 			// Default block name if it's not configured
18619 			newBlockName = newBlockName || 'P';
18620 
18621 			// Insert new block before/after the parent block depending on caret location
18622 			if (isCaretAtStartOrEndOfBlock()) {
18623 				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
18624 				if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
18625 					newBlock = createNewBlock(newBlockName);
18626 				} else {
18627 					newBlock = createNewBlock();
18628 				}
18629 
18630 				// Split the current container block element if enter is pressed inside an empty inner block element
18631 				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
18632 					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
18633 					newBlock = dom.split(containerBlock, parentBlock);
18634 				} else {
18635 					dom.insertAfter(newBlock, parentBlock);
18636 				}
18637 
18638 				moveToCaretPosition(newBlock);
18639 			} else if (isCaretAtStartOrEndOfBlock(true)) {
18640 				// Insert new block before
18641 				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
18642 				renderBlockOnIE(newBlock);
18643 			} else {
18644 				// Extract after fragment and insert it after the current block
18645 				tmpRng = rng.cloneRange();
18646 				tmpRng.setEndAfter(parentBlock);
18647 				fragment = tmpRng.extractContents();
18648 				trimLeadingLineBreaks(fragment);
18649 				newBlock = fragment.firstChild;
18650 				dom.insertAfter(fragment, parentBlock);
18651 				trimInlineElementsOnLeftSideOfBlock(newBlock);
18652 				addBrToBlockIfNeeded(parentBlock);
18653 				moveToCaretPosition(newBlock);
18654 			}
18655 
18656 			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
18657 			undoManager.add();
18658 		}
18659 
18660 		editor.onKeyDown.add(function(ed, evt) {
18661 			if (evt.keyCode == 13) {
18662 				if (handleEnterKey(evt) !== false) {
18663 					evt.preventDefault();
18664 				}
18665 			}
18666 		});
18667 	};
18668 })(tinymce);
18669 
18670